首页 > 其他分享 >[COCI2015-2016#1] RELATIVNOST 题解

[COCI2015-2016#1] RELATIVNOST 题解

时间:2024-07-22 16:11:46浏览次数:18  
标签:const 题解 COCI2015 times return text inline 2016 mint

前言

题目链接:洛谷

这道题有很多做法,但是模拟赛寄了,故记之。

题意简述

给你两个长为 \(n\) 的序列 \(A\) 和 \(B\),和一个常数 \(c \leq 20\),有 \(q\) 次修改。每次修改后求:

\[\large \sum _ {S \subseteq \lbrace i \rbrace _ {i = 1} ^ {n} \land |S| \geq c} \prod _ {x \in S} A_x \prod _ {x \not \in S} B_x \]

当然,对 \(10^4 + 7\) 取模。

题目分析

法 \(1\):线段树

先来考虑部分分。很明显的 DP,记 \(f[i][j]\) 表示在前 \(i\) 个中,选出了 \(j\) 个的乘积。转移很朴素,答案就是 \(\sum \limits _ {i = c} ^ {n} f[n][i]\)。发现对于本题,\(j \geq c\) 的状态意义相同,都表示满足了要求。所以不妨将状态压缩一下。可以得到如下转移方程:

\[f[i][j] = \begin{cases} f[i - 1][j] \times B_i & \text{ if } j = 0 \\ f[i - 1][j] \times B_i + f[i - 1][j - 1] \times A_i & \text{ if } 0 < j < c\\ f[i - 1][j] \times (A_i + B_i) + f[i - 1][j - 1] \times A_i & \text{ if } j = c \end{cases} \]

可以得到 \(30 \%\) 的部分分。考虑优化。这一看就矩阵啊,于是用上线段树优化矩乘,对于一个 \(i\),它的转移如下:

\[\begin{bmatrix} f_0 & f_1 & \cdots & f_{c - 1} & f_c \end{bmatrix} \begin{bmatrix} B_i & A_i & 0 & \cdots & 0 & 0 \\ 0 & B_i & A_i & \cdots & 0 & 0 \\ 0 & 0 & B_i & \cdots & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & A_i & 0 \\ 0 & 0 & 0 & 0 & B_i & A_i \\ 0 & 0 & 0 & 0 & 0 & A_i + B_i \end{bmatrix} \]

可是,\(\Theta(c ^ 3)\) 的复杂度怎么都接受不了啊,所以考虑换一个方向思考。

我们发现,对于一个位置的 \((A_i, B_i)\),它是什么时候加到状态里的不会影响最终结果。换句话说,我们 DP 的顺序没有要求。而且发现两个状态合并也很容易。比如合并 \(a\) 和 \(b\) 到 \(f\) 中,分别用 \(i\) 和 \(j\) 枚举 \(a\) 和 \(b\) 的状态,表示一个里面选出了 \(i\) 个位置,另一个里面选出了 \(j\) 个位置,对 \(f\) 的贡献可以表示为 \(f[\min \lbrace c, i + j \rbrace] \gets f[\min \lbrace c, i + j \rbrace] + a_i \times b_j\)。

发现,合并两个区间,就是线段树的工作。用个线段树维护,单点修改,查询根节点的 \(f[c]\)。果然,学矩阵学傻了。

时间复杂度:\(\Theta(c^2 (n + q \log n))\)。

法 \(2\):线段树分治

结合法 \(1\) 的物品添加顺序不会影响最终结果,并且问题可离线,每个物品出现的时间在时间轴上是连续的一段区间,添加物品很容易,想到线段树分治。没什么好说的了……剩下的就是板子了。

由于每次添加单个物品是 \(\Theta(c)\) 的,时间复杂度:\(\Theta((n + q) c \log q)\)。

法 \(3\):撤销背包

既然物品添加的顺序无关,如果我们想要修改某一个物品,那么把它看做是最后一个添加到状态里的,然后撤销,并增加修改后的物品。至于如何撤销,我们记添加 \((a, b)\) 物品前 DP 状态是 \(g\),添加后是 \(f\),根据我们上文提到的转移方程:

\[f[j] = \begin{cases} g[j] \times b & \text{ if } j = 0 \\ g[j] \times b + g[j - 1] \times a & \text{ if } 0 < j < c\\ g[j] \times (a + b) + g[j - 1] \times a & \text{ if } j = c \end{cases} \]

移项移一下,得:

\[g[j] = \begin{cases} f[j] \times b ^ {-1} & \text{ if } j = 0 \\ (f[j] - g[j - 1] \times a) \times b ^ {-1} & \text{ if } 0 < j < c \\ (f[j] - g[j - 1] \times a) \times (a + b) ^ {-1} & \text{ if } j = c \end{cases} \]

然而,如果 \(b\) 或 \(a + b\) 没有逆元,即 \(b \bmod 10^4 + 7 = 0\) 或 \((a + b) \bmod 10^4 + 7 = 0\) 时,我们无法撤销。怎么办呢?

首先,讨论两个东西太恶心了,考虑转化一下,之保留一个可能不存在逆元的数。

发现,\(b\) 是逃不掉的,而 \(a + b\) 是由于我们规定了大于等于 \(c\) 的看做相同。那这时候,为什么不能重新规定,只用计算恰好 \(< c\) 的状态,再用总方案数 \(\prod A_i + B_i\) 减去 \(\sum \limits _ {i < c} f[i]\) 呢?转移方程变为了:

\[f[j] = \begin{cases} g[j] \times b & \text{ if } j = 0 \\ g[j] \times b + g[j - 1] \times a & \text{ if } 0 < j < c\\ \end{cases} \]

即,少了最后一个 \(j = c\) 的转移。同理,移项移一下,得:

\[g[j] = \begin{cases} f[j] \times b ^ {-1} & \text{ if } j = 0 \\ (f[j] - g[j - 1] \times a) \times b ^ {-1} & \text{ if } 0 < j < c \\ \end{cases} \]

现在,我们只要考虑 \(b \bmod 10^4 + 7 = 0\) 的情况。发现如果有很多 \(b\) 都在模意义下为 \(0\),转移后的状态会全是 \(0\),此时,在继续转移下去不会变化。具体地,这个临界值是 \(c\),即如果有超过 \(c\) 个物品的 \(b\) 在模意义下为 \(0\),我们计算的 \(\sum \limits _ {i < c} f[i] = 0\)。而 \(c\) 又很小,我们完全可以在查询前暴力添加到状态中。所以,我们可以尝试用 multiset<> 来记录所有 \(b \bmod 10^4 + 7 = 0\) 的物品。

至于 \(\prod A_i + B_i\) 的撤销会涉及到 \(A_i + B_i\) 没有逆元,这时候,可以用一个 cnt 记录有多少满足的,以及用 tot 记录除了这些的乘积。这样删除的时候,只需要 \(cnt \gets cnt - 1\),而查询的时候,如果存在一个 \((A_i + B_i) \bmod 10^4 + 7 = 0\),我们让总方案数设为 \(0\),反之设为 \(tot\) 即可。

时间复杂度:\(\Theta(nc\log c + qc^2\log c)\)。实际上界很松。

代码

法 \(1\):线段树

法 \(2\):线段树分治

法 \(3\):撤销背包

优化了取模,超过了原来的 rank1。

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <cstring>
#include <vector>
#include <cstdio>
#include <set>
using namespace std;

namespace ModInt {
	using mint = short;
	using sint = int;
	
	constexpr const mint mod = 1e4 + 7;

	constexpr inline mint add(const mint a, const mint b) {
		return a + b >= mod ? a + b - mod : a + b;
	}

	constexpr inline mint sub(const mint a, const mint b) {
		return a - b < 0 ? a - b + mod : a - b;
	}

	constexpr inline mint mul(const mint a, const mint b) {
		return (sint(a)) * (sint(b)) % mod;
	}

	constexpr inline mint add(const mint a) { return a; }
	constexpr inline mint mul(const mint a) { return a; }

	template <typename... Types>
	constexpr inline mint add(const mint a, const Types... b) {
		return add(a, add(b...));
	}

	template <typename... Types>
	constexpr inline mint mul(const mint a, const Types... b) {
		return mul(a, mul(b...));
	}
	
	template <typename... Types>
	inline mint& toadd(mint &a, Types... b) {
		return a = add(a, b...);
	}
	
	template <typename... Types>
	inline mint& tomul(mint &a, Types... b) {
		return a = mul(a, b...);
	}
	
	inline mint& tosub(mint &a, const mint b) {
		return a = sub(a, b);
	}

	constexpr inline mint pow(const mint a, const mint p) {
		mint res = 1, base = a, b = p;
		while (b){
			if (b & 1) tomul(res, base);
			tomul(base, base), b >>= 1;
		}
		return res;
	}

	constexpr inline mint inv(const mint a) {
		return pow(a, mod - 2);
	}
}

using namespace ModInt;

namespace Fast{
	// fread(buf, 1, MAX, stdin)
	// fwrite(obuf, 1, o - obuf, stdout)
	
	constexpr const int MAX = 1 << 24, yzh_i_love_you = 1314520736;
	
	char buf[MAX], *p = buf, obuf[MAX], *o = obuf;
	
	#ifdef XuYueming
	# define fread(buf, size, n, file)
	#else
	# define getchar() (*p++)
	#endif
	#define putchar(c) (*o++ = c)
	
	constexpr inline bool isdigit(const char c) {	return c >= '0' && c <= '9'; }
	
	inline void read(int &x){
		x = 0; char c = 0;
		for (;!isdigit(c); c = getchar());
		for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
	}
	
	inline void write(int x){
		static short Stack[50], top = 0;
		do Stack[++top] = x % 10, x /= 10; while (x);
		while (top) *o++ = Stack[top--] | 48;
	}
}

using namespace Fast;

int n, m, k;
mint A[100010], B[100010];
multiset<mint> st;
int zero;
mint tot = 1;

mint f[22];

inline void modify(int p, mint a, mint b) {
	if (!B[p]) st.erase(st.find(A[p]));
	else {
		mint q = inv(B[p]);
		f[0] = mul(f[0], q);
		for (int i = 1; i <= k - 1; ++i)
			f[i] = mul(q, sub(f[i], mul(f[i - 1], A[p])));
	}
	if (add(A[p], B[p])) tomul(tot, inv(add(A[p], B[p])));
	else --zero;
	
	if (!b) st.insert(a);
	else {
		for (int i = k - 1; i; --i)
			f[i] = add(mul(f[i], b), mul(f[i - 1], a));
		f[0] = mul(f[0], b);
	}
	if (add(a, b)) tomul(tot, add(a, b));
	else ++zero;
	A[p] = a, B[p] = b;
}

inline mint query() {
	mint res = zero ? 0 : tot;
	if (st.empty()) {
		for (int i = 0; i <= k - 1; ++i)
			tosub(res, f[i]);
	} else if ((int)st.size() <= k) {
		static mint g[22];
		memcpy(g, f, sizeof (mint) * k);
		for (const auto& a: st) {
			for (int i = k - 1; i; --i)
				g[i] = mul(g[i - 1], a);
			g[0] = 0;
		}
		for (int i = 0; i <= k - 1; ++i)
			tosub(res, g[i]);
	}
	return res;
}

signed main() {
	fread(buf, 1, MAX, stdin);
	read(n), read(k);
	for (int i = 1, a; i <= n; ++i)
		read(a), A[i] = a % mod;
	for (int i = 1, b; i <= n; ++i)
		read(b), B[i] = b % mod;
	read(m), f[0] = 1;
	for (int i = 1; i <= n; ++i) {
		if (!B[i]) st.insert(A[i]);
		else {
			for (int j = k - 1; j; --j)
				f[j] = add(mul(f[j], B[i]), mul(f[j - 1], A[i]));
			f[0] = mul(f[0], B[i]);
		}
		if (add(A[i], B[i])) tomul(tot, add(A[i], B[i]));
		else ++zero;
	}
	for (int i = 1, p, a, b; i <= m; ++i) {
		read(p), read(a), read(b);
		modify(p, a % mod, b % mod), write(query()), putchar('\n');
	}
	fwrite(obuf, 1, o - obuf, stdout);
	return 0;
}

标签:const,题解,COCI2015,times,return,text,inline,2016,mint
From: https://www.cnblogs.com/XuYueming/p/18315964

相关文章

  • [ARC176E] Max Vector 题解
    题目链接点击打开链接题目解法这个题的一个转化很关键:把一次操作\(j\)等价看作必须满足\((\forall_{1\lei\len}X_i\gea_{j,i})|(\forall_{1\lei\len}Y_i\gea_{j,i})=1\)正确性是显然的另外的一个关键思路是网络流有了上面的转化,我们考虑切糕模型(其实接下来都好想......
  • 题解 B3694 数列离散化
    link简而言之,离散化就是把一个数列转化为由小到大的排名来缩小范围。离散化需要这个题不用数字本身。举个例子:-200244879914993235793离散化后就是:15243\(-2002\)最小,所以它对应\(1\)\(448799\)最大,所以它对应\(5\)实现考虑如何实现。首先由于离散化前后......
  • 【Web】ImaginaryCTF 2024 部分题解
    目录journalcrystalsP2CreadmeTheAmazingRacejournal简单的assert命令拼接payload:?file=test','..')===true||system("echo`tac/flag-cARdaInFg6dD10uWQQgm.txt`")||strpos('testcrystalsdocker-compose.yml里让服务报错读到泄露的hos......
  • 题解:牛客周赛 Round 52 A
    A两数之和时间限制:C/C++1秒,其他语言2秒空间限制:C/C++262144K,其他语言524288KSpecialJudge,64bitIOFormat:%lld题目描述对于给定的正整数\(z\),你需要寻找两个不同的正整数\(x\)和\(y\),使得\(x+y=z\)成立。如果不存在这样的\(x\)和\(y\),你只需要输出NO。......
  • ABC363 DEF 题解
    ABC363DEF题解前情提要:赛时过了ABCE。D-PalindromicNumber题目链接其实赛时已经看出了一些性质,但没想完做法,赛后看题解才发现这么简单/fn首先,为了方便,我们不把\(0\)视作回文数(因此需要特判一下\(n=1\)的情况)。下面要证明:\(d\)位回文数有\(10^{\left\lfloor\f......
  • 题解 P1115 最大子段和
    link容易想到朴素做法:for(intl=1;i<=n;++i){for(intr=1;j<=n;++j){intv=s[r]-s[l-1];ans=max(ans,v);}}但是显然\(\mathrm{\color{#052242}TLE}\)再回头看代码:想要v最大,只需要\(\large{S_{l-1}}\)最小即可......
  • 题解:P7482 不条理狂诗曲
    题解:P7482不条理狂诗曲本题解借鉴blossom_j大佬思路,但这位大佬的题解似乎没放正确代码。题意对于每一个\(a\)的子区间\(a_{l\dotsr}\),求选择若干个不连续的数的和的最大值,对答案取模\(10^{9}+7\)。思路主要算法:分治。计算跨过中点\(mid\)的区间的\(f\)之和。首......
  • P3041 [USACO12JAN] Video Game G 题解 AC自动机
    本题是一道AC自动机上的dp。首先不难想到状态定义f(i,j)表示仅考虑前i 个位置,第i 个字符是j 的分数,但无法转移,所以考虑将j这一维转化为表示AC自动机上的点。再定义val(i)表示以i 结尾的所有技能种数,则转移方程为f(i,j)=max(f(i,j),f(i-1,father(j)+val(j......
  • Loj P10064 黑暗城堡 题解 最短路径树记数
    这道题是一道最短路径树问题。首先,什么是最短路径树呢?定义一个图的最短路径树表示一个图上的生成树,且根节点到图上任一点的距离等于原图上两者之间的距离。而不难发现,题目其实是在求图上最短路径树记数。首先,建出最短路径树。枚举每条边,判断边两个端点到根的距离之差是否为......
  • 【题解】P4648 [IOI2007] pairs 动物对数
    Problem给定模板\(B(1\leB\le3)\),代表\(B\)维空间。其中有\(n\)个点,给出坐标与坐标上限\(M\),求\(n\)个点中曼哈顿距离\(\leD\)的对数。Solve\(B=1\)考虑将问题化简成:求\(\sum\limits_{i=1}^n\sum\limits_{j=1}^{i-1}[dis(i,j)\leqD]\)。其中\(dis(i,j)\)......