BZOJ4455 ZJOI2016小星星(容斥原理+树形dp)
相当于给树上的每个点分配一个编号使父亲和儿子间都有连边。
于是可以考虑树形dp:设f[i][j][k]为i号点的编号为j,其子树中编号集合为k的方案数。转移显然。然而复杂度3n·n3左右,具体我也不知道是多少,但肯定跑不过。
如果状态有集合的话不管怎样底数都是3了,考虑能不能变成2。完全不能可以想到容斥。
于是在dp中去掉k这一维。那么dp变成n3的。但是dp显然会有问题,即会出现不同的点取了相同编号的情况。这也可以看做是有编号未被选择。
那么就可以容斥了。先算出编号在全集中选择的答案,然后减去在全集去掉一个编号所得子集中选择的答案,再加上在全集去掉两个编号所得子集中选择的答案……通过组合数计算子集被计入的次数容易验证其正确性。
枚举子集然后dp,复杂度就是O(2n·n3)了。注意卡常。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } #define N 18 #define ll long long int n,m,a[N][N],stk[N],p[N],t; ll f[N][N],ans=0; struct data{int to,nxt; }edge[N<<1]; void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;} void dp(int k,int from,int s) { memset(f[k],0,sizeof(f[k])); for (int x=1;x<=s;x++) f[k][stk[x]]=1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { dp(edge[i].to,k,s); for (int x=1;x<=s;x++) { ll sum=0; for (int y=1;y<=s;y++) if (a[stk[x]][stk[y]]) sum+=f[edge[i].to][stk[y]]; f[k][stk[x]]=f[k][stk[x]]*sum; } } } void dfs(int k,int s) { if (k>n) { dp(1,1,s); ll sum=0; for (int i=1;i<=n;i++) sum+=f[1][i]; if (n-s&1) ans-=sum;else ans+=sum; return; } stk[s+1]=k;dfs(k+1,s+1); dfs(k+1,s); } int main() { #ifndef ONLINE_JUDGE freopen("bzoj4455.in","r",stdin); freopen("bzoj4455.out","w",stdout); const char LL[]="%I64d"; #else const char LL[]="%lld"; #endif n=read(),m=read(); while (m--) { int x=read(),y=read(); a[x][y]=a[y][x]=1; } for (int i=1;i<n;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } dfs(1,0); cout<<ans; return 0; }