「Luogu P4516」[JSOI2018] 潜入行动
题目
外星人又双叒叕要攻打地球了,外星母舰已经向地球航行!这一次,
JYY已经联系好了黄金舰队,打算联合所有JSOIer抵御外星人的进攻。在黄金舰队就位之前,
JYY打算事先了解外星人的进攻计划。现在,携带了监听设备的特工已经秘密潜入了外星人的母舰,准备对外星人的通信实施监听。外星人的母舰可以看成是一棵 \(n\) 个节点、 \(n-1\) 条边的无向树,树上的节点用 \(1,2,\cdots,n\) 编号。
JYY的特工已经装备了隐形模块,可以在外星人母舰中不受限制地活动,可以神不知鬼不觉地在节点上安装监听设备。如果在节点 \(u\) 上安装监听设备,则
JYY能够监听与 \(u\) 直接相邻所有的节点的通信。换言之,如果在节点 \(u\) 安装监听设备,则对于树中每一条边 \((u,v)\) ,节点 \(v\) 都会被监听。特别注意放置在节点 \(u\) 的监听设备并不监听 \(u\) 本身的通信,这是JYY特别为了防止外星人察觉部署的战术。
JYY的特工一共携带了 \(k\) 个监听设备,现在JYY想知道,有多少种不同的放置监听设备的方法,能够使得母舰上所有节点的通信都被监听?为了避免浪费,每个节点至多只能安装一个监听设备,且监听设备必须被用完。
分析
求解以 \(u\) 为根的子树的答案时,如果是考虑整个子树都被覆盖的话,那么求解 \(fa[u]\) 的答案时,显然如果在 \(fa[u]\) 放置监听设备,那么 \(u\) 未被覆盖的情况也应该计入 \(fa[u]\) 的答案,因此要分别求解 \(u\) 是否被覆盖的答案,此外,用儿子结点 \(v\) 的答案更新 \(u\) 的答案时,若 \(u\) 不放置监听设备,要覆盖 \(u\) 的话至少有一个儿子结点要放置监听设备,而儿子结点不放置监听设备的答案也会更新 \(u\) 的答案,因此还需要用一维枚举是否在该店放置监听设备。
综上,我们设 \(f[u][i][0 / 1][0 / 1]\) 表示当前考虑到结点 \(u\)、在 \(u\) 为根的子树内一共放置 \(i\) 个监听设备、\(u\) 结点是否放置监听设备、\(u\) 结点是否被儿子结点监听到。于是可以得到以下状态转移方程(较复杂,细节有点小多):
这时候会发现复杂度是 \(O(nk^2)\),显然没法过。由于 \(k\) 比较小,而且如果 \(size[u]<k\) 的话,枚举第二维的时候只需要枚举到(还没用当前枚举的儿子结点的 \(size[v]\) 更新的)\(size[u]\) 就好了,这个优化看似是常数级别的,其实它实际的复杂度只有 \(O(nk)\). 考虑结点 \(u\), 它的儿子结点中,最多只有 \(size[u] / k\) 个 \(size[v]>k\) 的结点,对于这一部分儿子结点,我们枚举 \(j\) 这一部分的总次数加起来是 \(\sum_{v \in son[u]} size[v] / k \cdot k=\sum_{v\in son[u]}size[v]\), 对于 \(size[v]\leq k\) 这部分的结点,枚举 \(j\) 的次数加起来也是 \(\sum_{v \in son[u]}size[v]\) 的,合起来就是 \(size[u]\), 而枚举 \(i\) 的次数显然是 \(\leq k\) 的,于是在结点 \(u\) 这一局部的贡献是 \(\leq k \cdot |son[u]|\) 的,相当于这个优化直接去掉了一维 \(k\), 从而最后整棵树的枚举次数是 \(O(k \cdot \sum_{u \in V}|son[u]|) = O(nk)\).
代码
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 1e5 + 5;
constexpr i64 mod = 1e9 + 7;
int n, k;
std::vector<int> g[N];
int sz[N];
int f[N][101][2][2], t[N][101][2][2];
int mul(int x, int y) {
return (int)((1ll * x * y) % mod);
}
int add(int x, int y) {
return (int)((1ll * x + 1ll * y) % mod);
}
void dfs(int u, int fa) {
sz[u] = 1;
f[u][0][0][0] = f[u][1][1][0] = 1;
for (int v : g[u]) {
if (v == fa) continue;
dfs(v, u);
for (int i = 0; i <= std::min(sz[u], k); ++i) { // 注意更新顺序导致需要考虑的细节:备份枚举 v 之前的答案然后再用它来计算
t[u][i][0][0] = f[u][i][0][0], f[u][i][0][0] = 0;
t[u][i][0][1] = f[u][i][0][1], f[u][i][0][1] = 0;
t[u][i][1][0] = f[u][i][1][0], f[u][i][1][0] = 0;
t[u][i][1][1] = f[u][i][1][1], f[u][i][1][1] = 0;
}
for (int i = 0; i <= std::min(sz[u], k); ++i) {
for (int j = 0; j <= std::min(sz[v], k - i); ++j) {
f[u][i+j][0][0] = add(f[u][i+j][0][0],
mul(t[u][i][0][0], f[v][j][0][1]));
f[u][i+j][0][1] = add(f[u][i+j][0][1],
add(mul(t[u][i][0][0], f[v][j][1][1]),
mul(t[u][i][0][1],
add(f[v][j][0][1], f[v][j][1][1]))));
f[u][i+j][1][0] = add(f[u][i+j][1][0],
mul(t[u][i][1][0], add(
f[v][j][0][0], f[v][j][0][1])));
f[u][i+j][1][1] = add(f[u][i+j][1][1],
mul(t[u][i][1][1], add(
add(f[v][j][1][0], f[v][j][1][1]),
add(f[v][j][0][1], f[v][j][0][0]))));
f[u][i+j][1][1] = add(f[u][i+j][1][1],
mul(t[u][i][1][0],
add(f[v][j][1][0], f[v][j][1][1])));
}
}
sz[u] += sz[v];
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> k;
for (int i = 1, u, v; i < n; ++i) {
std::cin >> u >> v;
g[u].push_back(v), g[v].push_back(u);
}
dfs(1, 0);
std::cout << add(f[1][k][1][1], f[1][k][0][1]);
return 0;
}

浙公网安备 33010602011771号