下笔春蚕食叶声。

【ZJOI2016】小星星 容斥+树形DP

题意

UOJ #185.
给出一个\(n(n \le 17)\) 的树和 \(n\) 个点的图,求有多少种树点和图点的对应方案是“好的”
“好的”方案,如果现在树上两点间存在边,那么要求图上对应的这两点之间也有边。

题解

考虑用 \(dp\) 去计算方案数,如果需要确定一个顺序来dp,显然是从叶子向上树形dp。
不妨设 1 为整棵树的根,现在用 \(f_{i, j}\) 表示树上一颗以 \(i\) 为根的子树全部确定对应点,\(i\) 树点对应 \(j\) 图点。

\[f_{u, i} = \prod_v (\sum_j f_{v, j}) \]

但这样计算有一个问题,会有多个树点选择了同一个图点,会算重。
这时注意到 \(n\) 非常小,因此可以容斥。
状态为\(1\) 的图点可以选择,其他不能。然后每个状态计算一下,最后统计就好。

总的来说就是先容斥再每个状态都树形DP。

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
typedef long long ll;
const int N = 18;
int n, m, cnt, id[N];
int e, to[N << 1], nxt[N << 1], hd[N];
ll f[N][N];
bool ed[N][N];
void add(int u, int v){
	to[++e] = v; nxt[e] = hd[u]; hd[u] = e;
}
void dfs(int u, int fa){
	for(int i = hd[u]; i; i = nxt[i]){
		int v = to[i]; if(v == fa) continue;
		dfs(v, u);
	}
	for(int i = 1; i <= cnt; i++){
		f[u][i] = 1;
		for(int j = hd[u]; j; j = nxt[j]){
			int v = to[j]; if(v == fa) continue;
			ll sum = 0;
			for(int k = 1; k <= cnt; k++)
				if(ed[id[i]][id[k]]) sum += f[v][k];
			f[u][i] *= sum;
		}
	}//树形DP
	return;
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1, u, v; i <= m; i++)
		scanf("%d%d", &u, &v), ed[u][v] = ed[v][u] = 1;
	for(int i = 1, u, v; i < n; i++)
		scanf("%d%d", &u, &v), add(u, v), add(v, u);
	ll ans = 0;
	for(int i = 0; i < (1 << n); i++){
		cnt = 0;
		for(int j = 1; j <= n; j++)
			if(i & (1 << (j - 1)))
				id[++cnt] = j;
		dfs(1, 0);
		ll sum = 0;
		for(int j = 1; j <= cnt; j++)
			sum += f[1][j];
		ans += (((cnt ^ n) & 1) ? -1 : 1) * sum;
	}//容斥
	printf("%lld\n", ans);
	return 0;
}
posted @ 2021-06-24 15:36  ACwisher  阅读(44)  评论(0编辑  收藏  举报