[JSOI2018]潜入行动

首先定义一个比较显然的状态:\(f[i][j][4]\),其中

\(f[i][j][0]\)表示以\(i\)为根的子树安装了\(k\)个监视器,且节点\(i\)安装了监视器并被监听;

那么,相同地

\(f[i][j][1]\)表示节点\(i\)安装了监视器且不被监听

\(f[i][j][2]\)表示节点\(i\)未安装监视器且被监听

\(f[i][j][3]\)表示节点\(i\)未安装监视器且不被监听

注意,上述状态内的是否被监听均指是否被自己的儿子节点监听。

然后,我们按照常规的树形背包转移套路对状态进行转移,于是我们就有了如下转移方程:

\[f[u][i][3] = \sum f[v][j][2] * f[u][i-j][3] \]

\[f[u][i][2] = \sum (f[v][j][0]+f[v][j][2]) * (f[u][i-j][2]+f[u][i-j][3]) \]

\[f[u][i][1] = \sum (f[v][j][2]+f[v][j][3]) * f[u][i-j][1] \]

\[f[u][j][0] = \sum (f[v][j][0]+f[v][j][1]+f[v][j][2]+f[v][j][3]) * (f[u][i-j][0]+f[u][i-j][1]) \]

然后,我们发现在转移\(0\)\(2\)两个状态的时候,因为我们无法枚举每个子节点是否有安装的或都不安装,但我们可以知道如果当前节点没有被监听,子节点肯定都没有安装,所以我们可以利用这个把多余的状态去掉:

\[f[u][i][0] -= f[u][i][1]\ \ \ \ \ f[u][i][2] -= f[u][i][3] \]

还有一点需要注意,因为我们是在做树上的计数类\(DP\)问题,所以在考虑当前节点的一个子节点时,剩下子节点的状态不能保留,要临时记录下来,防止转移时混乱或出现各种玄学错误。

然后就是复杂度的分析:

略微思考一下你会发现,在进行上述\(DP\)转移的时候,我们的时间复杂度疑似是\(O(nk^2)\),但只要我们枚举转移的时候边界控制的足够优美,它的时间复杂度会进一大步下降到\(O(nk)\),所以在本题\(k \leq 100\)的情况下,完全是可过的。

其实对于边界的控制也很简单,我们只需注意枚举\(i\)的时候不能超出当前子树的大小,枚举\(j\)的时候要注意\(i-j\)不能大于\(siz[u]-siz[v]\)且也要小于子树大小。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#define rint register int
#define LL long long
 
const int maxn = 1e5 + 5;
const int MOD = 1e9 + 7;
int n, k, tot, head[maxn], f[maxn][105][4], siz[maxn], g[105][4];

/*
0 -> 安装且被监听
1 -> 安装且不被监听
2 -> 不安装被监听
3 -> 不安装不被监听
*/

struct Edge {
	int to, nxt;
	Edge(int _to, int _nxt) {
		this -> to = _to;
		this -> nxt = _nxt;
	} Edge(){}
}edge[maxn << 1];

void add(int from, int to) {edge[++tot] = Edge(to, head[from]), head[from] = tot;}

template<class T>
inline T read(T &x) {
	x = 0; int w = 1, ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - 48;
	return x *= w;
} 

void dfs(int u, int fa) {
	f[u][1][1] = 1; f[u][0][3] = 1; siz[u] = 1;
	for (rint i = head[u]; i; i = edge[i].nxt) {
		int v = edge[i].to; 
		if (v == fa) continue;
		dfs(v, u);
		siz[u] += siz[v]; memcpy(g, f[u], sizeof(g));
		for (rint j = min(siz[u], k); ~j; j--) f[u][j][0] = f[u][j][1] = f[u][j][2] = f[u][j][3] = 0;
		for (rint j = min(siz[u], k); ~j; j--) {
			for (rint p = max(0, j + siz[v] - siz[u]); p <= min(siz[v], j); p++) {
				f[u][j][3] += ((LL)f[v][p][2] * g[j - p][3]) % MOD, f[u][j][3] %= MOD;
				f[u][j][2] += ((LL)(f[v][p][0] % MOD + f[v][p][2] % MOD) % MOD) *
							  ((g[j - p][2] % MOD + g[j - p][3] % MOD) % MOD) % MOD; 
				f[u][j][2] %= MOD;
				f[u][j][1] += ((LL)(f[v][p][2] % MOD + f[v][p][3] % MOD)) % MOD * 
							  g[j - p][1] % MOD; f[u][j][1] %= MOD;
				f[u][j][0] += ((LL)(f[v][p][0] + f[v][p][1] % MOD) + (f[v][p][2] + f[v][p][3]) % MOD)
				 			  % MOD * ((g[j - p][1] % MOD+ g[j - p][0] % MOD)) % MOD; f[u][j][0] %= MOD;
			}
			f[u][j][0] = (f[u][j][0] - f[u][j][1] + MOD) % MOD; 
			f[u][j][2] = (f[u][j][2] - f[u][j][3] + MOD) % MOD; 	
		}
	}
}

int main() {
	int x, y;
	read(n), read(k);
	for (int i = 1; i < n; i++) {
		read(x), read(y);
		add(x, y), add(y, x);
	}
	dfs(1, 0);
	std::cout << f[1][k][0] + f[1][k][2]<< '\n';
	return 0;
}

后话

我和@Flandre_495一同做此题的过程:

我:又是计数类\(DP\)……

F:你感觉复杂度多少?

我:三维状态,然后树形背包平凡,大概\(O(nk)\)吧。

随手向下一翻,嗯!?\(k \leq min(n,100)\)!!

我们俩同时握住对方的手:水黑题,切了他!

\(………………One\ and\ a\ half\ hours\ later\)

我和@Flandre_495对视一眼:确认过眼神,这是道黑题……

完结撒花♪(・ω・)ノ

posted @ 2019-11-08 08:25  Hydrogen_Helium  阅读(171)  评论(0编辑  收藏  举报