bzoj 4455
容斥好题
首先我们考虑,如果没有节点之间一一对应的限制,我们可以这样$dp$:
设状态$dp[i][j]$表示以$i$为根节点的子树,节点$i$与节点$j$对应的方案数
那么转移就是$dp[i][j]=\prod_{son_{i}}\sum_{k=1}^{n}map[j][k]dp[son_{i}][k]$
即枚举每个子节点与哪个点相对应,保证父子节点之间有连边
这个$dp$的时间复杂度是$O(n^{3})$的
但是有个问题,这样显然会产生大量的重复,因为会包括进去多个点对应同一个点的情况
那么我们考虑容斥:
有多个点对应同一个点等价于有一部分点没有点对应
因此我们枚举那一部分点没有被对应,然后做个容斥即可,容斥系数为$-1$的选出点集大小次幂
每次枚举出一个点集之后都需要重新进行$dp$,$dp$过程中保证不使用到不允许用的点即可
时间复杂度$O(2^{n}n^{3})$,bzoj严重卡常,需要足够强的卡常技巧
贴代码:
#include <cstdio> #define ll unsigned long long #define uint unsigned int struct Edge { uint next; uint to; }edge[37]; uint head[18]; uint cnt=1; uint my_stack[18]; uint ttop=0; inline void add(uint l,uint r) { edge[cnt].next=head[l]; edge[cnt].to=r; head[l]=cnt++; } ll dp[18][18]; uint acc[18][18]; uint n,m; void dfs(uint x,uint fx) { for(register uint i=1;i<=ttop;++i)dp[x][my_stack[i]]=1; uint las=0; for(register uint i=head[x];i;i=edge[i].next) { uint to=edge[i].to; if(to==fx) { if(las)edge[las].next=edge[i].next; else head[x]=edge[i].next; continue; } las=i; dfs(to,x); for(register uint j=1;j<=ttop;++j) { ll sum=0; for(register uint k=1;k<=ttop;++k)if(acc[my_stack[j]][my_stack[k]])sum+=dp[to][my_stack[k]]; dp[x][my_stack[j]]*=sum; } } } inline uint read() { uint f=1,x=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { n=read(),m=read(); for(register uint i=1;i<=m;++i) { uint x=read(),y=read(); acc[x][y]=acc[y][x]=1; } for(register uint i=1;i<n;++i) { uint x=read(),y=read(); add(x,y),add(y,x); } ll ans=0; for(register uint i=0;i<(1<<n)-1;++i) { ttop=0; int x=1; for(register uint j=1;j<=n;++j) { if((1<<(j-1))&i)x=-x; else my_stack[++ttop]=j; } dfs(1,1); for(register uint j=1;j<=ttop;++j) { if(x==-1)ans-=dp[1][my_stack[j]]; else ans+=dp[1][my_stack[j]]; } } printf("%llu\n",ans); return 0; }