前言
题目链接:洛谷。
这道题有很多做法,但是模拟赛寄了,故记之。
题意简述
给你两个长为 \(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)\)。实际上界很松。
代码
法 \(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