[BZOJ4455][ZJOI2016]数星星(容斥DP)
4455: [Zjoi2016]小星星
Time Limit: 10 Sec Memory Limit: 512 MB
Submit: 707 Solved: 419
[Submit][Status][Discuss]Description
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n?1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。Input
第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。保证这些小星星通过细线可以串在一起。n<=17,m<=n*(n-1)/2Output
输出共1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。Sample Input
4 3
1 2
1 3
1 4
4 1
4 2
4 3Sample Output
6HINT
Source
一个很好理解的题解:https://blog.csdn.net/johann_oier/article/details/51090513
思路就是:如果集合可重,那么我们对于以i为根的子树,直接枚举这些节点的集合,然后f[i][j]表示i映射到原图的j的方案数。
那么现在集合不可重怎么办呢?去掉这个限制的一个好办法就是容斥,显然最后整棵树对应的集合是全集,那么我们将映射集合为全集的方案数-映射集合大小为n-1的方案数+大小为n-2的方案数-...,最后就是答案了。
#include<cstdio> #include<algorithm> #define rep(i,l,r) for (int i=(l); i<=(r); i++) typedef long long ll; using namespace std; const int N=20; ll f[N][N]; int n,m,ed,x,y,cnt,hash[N],g[N][N],mp[N][N],fa[N],p[N],q[N],h[N],to[N],nxt[N]; void add(int u,int v){ to[++cnt]=v; nxt[cnt]=h[u]; h[u]=cnt; } void bfs(int s){ int l=1,r=1; q[1]=s; while (l<=r){ int x=q[l],c=0; l++; rep(i,1,n) if (mp[x][i] && i!=fa[x]) q[++r]=i,fa[i]=x,++c,add(x,i); if (!c) hash[x]=1; } } void dp(int s,int sta,int cnt){ for (int i=n; i; i--){ int x=q[i]; if (hash[x]) continue; rep(j,1,cnt){ for (int i=h[x],k; i; i=nxt[i]) if (fa[k=to[i]]==x){ ll sm=0; rep(l,1,cnt) if (g[p[j]][p[l]]) sm+=f[k][l]; f[x][j]*=sm; } } } } int get(int x){ int cnt=0; for (int j=1; x; x>>=1,j++) if (x & 1) p[++cnt]=j; x>>=1; return cnt; } int main(){ freopen("bzoj4455.in","r",stdin); freopen("bzoj4455.out","w",stdout); scanf("%d%d",&n,&m); rep(i,1,n) g[i][i]=1; rep(i,1,m) scanf("%d%d",&x,&y),g[x][y]=g[y][x]=1; rep(i,1,n-1) scanf("%d%d",&x,&y),mp[x][y]=mp[y][x]=1; bfs(1); ll ans=0; int tag=n&1; for (int sta=1; sta<(1<<n); sta++){ int cnt=get(sta); ll flag=((cnt&1)==tag)?1:-1,s=0; rep(i,1,n) rep(j,1,cnt) f[i][j]=1; dp(1,sta,cnt); rep(i,1,cnt) s+=f[1][i]; ans+=flag*s; } printf("%lld\n",ans); return 0; }