[COCI2015-2016#1] RELATIVNOST 题解

前言

题目链接:洛谷

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

题意简述

给你两个长为 \(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;
}
posted @ 2024-07-22 15:57  XuYueming  阅读(4)  评论(0编辑  收藏  举报