UOJ#185. 【ZJOI2016】小星星 容斥原理 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ185.html
题解
首先暴力DP是 $O(3^nn^3)$ 的,大家都会。
我们换个方向考虑。
假设我们求的是树上每一个节点到图上的节点的映射,而且图上的一个点可以被树上多个点映射到,那么就是求图上所有点都被映射到至少一次的方案数。
我们发现保证所有点都被映射到会很麻烦,所以我们考虑容斥。
枚举哪些点一定没有被映射到,答案就是至少0个点没被映射到的 - 至少1个点的 + 至少2个点的 ……
已经确定哪些点没有被映射到之后就好办了,设 $dp[i][j]$ 表示树上节点 $i$ 对应图上节点 $j$ 时,子树 $i$ 的方案数。
转移比较容易想到,不说了。
时间复杂度 $O(2^n n^3)$ 。
代码
#include <bits/stdc++.h> #define clr(x) memset(x,0,sizeof (x)) using namespace std; typedef long long LL; LL read(){ LL x=0,f=0; char ch=getchar(); while (!isdigit(ch)) f|=ch=='-',ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return f?-x:x; } const int N=20; int n,m; vector <int> e[N]; int g[N][N],f[N]; LL dp[N][N]; int cnt1(int x){ int ans=0; while (x) x-=x&-x,ans++; return ans; } void dfs(int x,int pre){ for (int i=1;i<=n;i++) if (!f[i]) dp[x][i]=1; for (auto y : e[x]) if (y!=pre){ dfs(y,x); for (int i=1;i<=n;i++) if (!f[i]){ LL tot=1; for (int j=1;j<=n;j++) if (!f[j]&&g[i][j]) tot+=dp[y][j]; dp[x][i]*=tot; } } } int main(){ n=read(),m=read(); for (int i=1;i<=m;i++){ int x=read(),y=read(); g[x][y]=g[y][x]=1; } for (int i=1;i<n;i++){ int x=read(),y=read(); e[x].push_back(y); e[y].push_back(x); } LL ans=0; for (int i=0;i<(1<<n);i++){ clr(f); LL c=(cnt1(i)&1)?-1:1; for (int j=0;j<n;j++) if (i>>j&1) f[j+1]=1; clr(dp); dfs(1,0); for (int j=1;j<=n;j++) ans+=c*dp[1][j]; } cout<<ans<<endl; return 0; }