本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作。
本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
转载请注明出处,侵权必究,保留最终解释权!
题目链接:BZOJ4455
正解:DP+容斥原理
解题报告:
考虑暴力的话需要将一棵子树中的点去与另一个集合一一配对,而对于这种计数类问题,我们可以通过容斥来避开限制。
也就是说不管非法情况,用容斥的方法直接统计,最后得到答案。
我们先枚举整棵树对应的集合(一个映射),用f[i][j]表示以i为根的子树且i对应j的方案数,每次枚举根对应一个节点,再枚举每个儿子节点对应节点,乘起来就可以了。
考虑我这样做,显然会有大量节点映射到一个节点上去了,所以用容斥原理同加异减一下,把每个状态的ans综合起来就好了。
然而复杂度还是很虚,写了一发,然后被卡常了…
改变循环变量顺序,预处理可行的转移对象,终于AC了...
//It is made by ljh2000 #include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; typedef long long LL; #define RG register const int MAXN = 19; const int MAXM = 520; int n,m,ecnt,first[MAXN],to[MAXM],next[MAXM],all; int mp[MAXN][MAXN],cnt,a[MAXN]; int head[MAXN],match[MAXN]; LL ans,f[MAXN][MAXN];//f[i][j]表示以i为根的子树的方案数,其中i对应的是j struct edge{ int to,next; }e[MAXM]; inline void link(int x,int y){ next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; } inline void Link(int x,int y){ e[++ecnt].next=head[x]; head[x]=ecnt; e[ecnt].to=y; } inline int getint(){ int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w; } inline void dp(RG int x,RG int fa){ RG int i,s1,j,k; for(i=first[x];i;i=next[i]) { if(to[i]==fa) continue; dp(to[i],x); } RG LL now=0; for(s1=1;s1<=cnt;++s1) {//枚举当前子树的根选什么 f[x][s1]=1; for(j=first[x];j;j=next[j]) { if(to[j]==fa) continue; now=0; for(k=head[a[s1]];k;k=e[k].next) now+=f[to[j]][ match[e[k].to] ]; f[x][s1]*=now; if(f[x][s1]==0) break;//剪枝... } } } inline void work(){ n=getint(); m=getint(); all=(1<<n)-1; RG int i,j,x,y; RG LL now; for(i=1;i<=m;++i) { x=getint(); y=getint(); mp[x][y]=mp[y][x]=1; } for(i=1;i<n;++i) { x=getint(); y=getint(); link(x,y); link(y,x); } for(i=1;i<=all;++i) { x=i; cnt=0; for(j=1;j<=n;++j) { if(x&1) a[++cnt]=j; match[j]=cnt; x>>=1; if(x==0) break; } ecnt=0; memset(head,0,sizeof(head)); for(j=1;j<=cnt;j++) for(y=1;y<=cnt;y++) if(mp[ a[j] ][ a[y] ]) Link(a[j],a[y]); dp(1,0); now=0; for(j=1;j<=cnt;++j) now+=f[1][j]; if((cnt&1) == (n&1)) ans+=now; else ans-=now; } printf("%lld",ans); } int main() { work(); return 0; }