【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;
}
QwQwQ