[ZJOI2016]小星星
动态规划$+$容斥原理:
1、暴力状压
我们尝试设计状态:$$f[i][j][S]$$
表示新的图上点$i$对应旧的图上点$j$并且所有点的状态为一个二进制数$S$时的方案数
那么只需要暴力树形$dp$就行了
但是这样做为什么说是暴力呢。。。一看就知道,复杂度爆炸,然而我并不会证$qwq$
2、正解(可能吧):容斥原理
我们思考上面这种方法为什么会爆炸,就是因为有了最后那维状态,如果我们可以省去那个状态的话,就可以降低时间复杂度了,于是我们设:
$$f[i][j]$$表示新的图上点$i$对应旧的图上点$j$时的方案数
那么我们就面临一个问题,我们只需要$n$个点在集合内的方案数,但是$n-1,n-2,\cdots$的点的方案数我们都算进来了
那么我们算一下$n-1$的答案,这些方案数包括$n-2,n-3,\cdots$等的方案数
但是我们发现我们总共减了两次$n-2$的方案数,所以再加上$n-2$的答案,但是又多了$n-3$等,我们发现:一加一减到最后刚好就是我们要的$n$个点的方案数
所以:$|S|=n$时的方案数减去$|S|=n-1$时的方案数加上$|S|=n-2$时的方案数减去$|S|=n-3$时的方案数$......$
一加一减就阔以了
接下来是美滋滋的代码时间~~~
#include<iostream> #include<cstdio> #include<cstring> #define ll long long #define N 19 #define M 140 using namespace std; struct Edge { int to,nxt; }edge[N<<1]; int n,m,cnt; ll ans; int ban[N],head[N],g[N][N]; ll f[N][N]; void Add(int u,int v) { edge[++cnt]=(Edge){v,head[u]}; head[u]=cnt; } void Dfs(int u,int fa) { for(int i=1;i<=n;++i) f[u][i]=1; for(int i=head[u];i;i=edge[i].nxt) { int v=edge[i].to; if(v==fa) continue; Dfs(v,u); for(int j=1;j<=n;++j) { ll sum=0; for(int k=1;k<=n;++k) sum+=f[v][k]*(g[k][j]&&ban[j]&&ban[k]); f[u][j]*=sum; } } } signed main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;++i) { int u,v; scanf("%d%d",&u,&v); g[u][v]=g[v][u]=1; } for(int i=1;i<n;++i) { int u,v; scanf("%d%d",&u,&v); Add(u,v); Add(v,u); } for(int i=0;i<(1<<n);++i) { memset(ban,0,sizeof(ban)); ll ret=0; int size=n,now=i; for(int j=1;now;now>>=1,++j) ban[j]=(now&1),size-=(now&1); // for(int j=1;j<=n;++j) // printf("%d ",ban[j]); // printf("\n"); Dfs(1,0); for(int j=1;j<=n;++j) ret+=f[1][j]; if(size%2) ans-=ret;//printf("%d\n",ans); else ans+=ret; } printf("%lld",ans); return 0; }