2022.11.16
上午打模拟赛,用线段树结果被疯狂卡常,人已经被卡没了。
对,人没了。
补题
Cover
状态:
\(\large{f_{i,j,k}(k\in \left(0,1,2\right))}\) 表示 \(i\) 子树中(不考虑 \(i\) 的父节点),有 \(j\) 个节点被覆盖,状态为 \(k\)。
- \(k = 0\):节点 \(i\) 和节点 \(i\) 的所有子节点都没有被选择;
- \(k = 1\):节点 \(i\) 被选择;
- \(k = 2\):节点 \(i\) 没有被选择,但有 \(i\) 的子节点被选择。
转移的话不如直接放代码,方便一点(其实就是懒)。
不要被展开的赫鲁晓夫楼吓到了,本来可以用 for 枚举,但为了更具体地解释还是展开了
code
int size[N];
int f[N][N][3];
int tmp[N][3];// tmp用于转移时临时存储,避免重复更新
void DP(int u, int fa){
size[u] = 1;
f[u][0][0] = 1;
f[u][1][1] = 1;
for(int i = head[u]; i; i = e[i].nextt){
int v = e[i].to;
if(v == fa)continue;
DP(v, u);
memset(tmp, 0, sizeof(tmp));
for(int j = 0; j <= size[u]; ++j){
for(int k = 0; k <= size[v]; ++k){
ad(tmp[j + k][0], 1ll * f[u][j][0] * f[v][k][0] % mod);
// f[u][j][0]之前没有子节点被选,f[v][k][0]这个子节点也不选。
ad(tmp[j + k][0], 1ll * f[u][j][0] * f[v][k][2] % mod);
// f[v][k][2]这个子节点也不选,但被覆盖过。
ad(tmp[j + k + 1][1], 1ll * f[u][j][1] * f[v][k][0] % mod);
// f[u][j][1]u被选了,f[v][k][0]这个子节点不选且没有被覆盖。
// !!注意,因为v没有被覆盖而u选了,u为根的树下就会多一个v被覆盖,所以是j+k+1。
ad(tmp[j + k][1], 1ll * f[u][j][1] * f[v][k][1] % mod);
// f[u][j][1]u被选了,f[v][k][1]v也被选了。
ad(tmp[j + k][1], 1ll * f[u][j][1] * f[v][k][2] % mod);
// f[u][j][1]u被选了,f[v][k][2]v没被选,但被覆盖了。
// [2]的转移比较重点。
ad(tmp[j + k + 1][2], 1ll * f[u][j][0] * f[v][k][1] % mod);
// f[u][j][0]之前的子节点没有覆盖过u,f[v][k][1]当前的v成了第一个覆盖u的,在这里j+k+1。
ad(tmp[j + k][2], 1ll * f[u][j][2] * f[v][k][0] % mod);
ad(tmp[j + k][2], 1ll * f[u][j][2] * f[v][k][1] % mod);
ad(tmp[j + k][2], 1ll * f[u][j][2] * f[v][k][2] % mod);
// f[u][j][2]之前的子节点覆盖过u,f[v][k]可以随便选。
}
}
size[u] += size[v];
for(int j = 0; j <= size[u]; ++j)
for(int l = 0; l <= 2; ++l)
f[u][j][l] = tmp[j][l];// 还给f。
}
return ;
}
注意这里 \(size\) 的写法,这样写可以把 \(\Theta(n^3)\) 优化成 \(\Theta(n^2)\)。
证明:枚举 \(j\) 在时间上相当于把 \(v\) 之前的所有子树里的节点全部枚举一遍,这时再枚举一遍 \(v\) 的节点,在时间上相当于把 \(v\) 之前的子树里的节点和 \(v\) 子树里的节点枚举配对,这时我们发现对于任意两点,它们只会在 LCA 处被配对一次,这样的时间复杂度就一定是 \(\Theta(n^2)\) 的了。