BZOJ 4455: [Zjoi2016]小星星(容斥+樹形dp)
阿新 • • 發佈:2018-12-03
解題思路
首先題目中有兩個限制,第一個是兩個集合直接必須一一對映,第二個是重新標號後,\(B\)中兩點有邊\(A\)中也必須有。發現限制\(2\)比較容易滿足,考慮化簡限制\(1\)。令\(f(S)\)表示重標號後至多出現在\(S\)中的標號且滿足條件\(2\)的方案數,令\(g(S)\)表示重標號後恰好出現在\(S\)中的標號滿足條件\(2\)的方案數。這應該是容斥裡的一個套路。那麼有轉移方程:
\[ f(S)=\sum\limits_{T \subseteq S}g(T)\Rightarrow g(S)=\sum\limits_{T \subseteq S}(-1)^{\left|S\right |-\left|T\right|}f(T) \]
然後問題就轉化成為求\(f(S)\)了。令\(h[i][j]\)表示\(i\)點重新標號後是\(j\)的方案數。那麼轉移的時候考慮\(x\)的兒子\(u\)產生的貢獻,可以列舉\(u\)的重標號,然後看他們兩個的重標號之間有沒有邊,如果有的話\(cnt+=h[u][j]\),最後\(u\)對\(h[i][j]\)的貢獻就為\(cnt\)。時間複雜度\(O(2^n*n^3)\),在下人醜常數大,\(bzoj\)卡了半天才過。。
程式碼
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<cstdlib> using namespace std; const int MAXN = 18; typedef long long LL; inline int rd(){ int x=0,f=1;char ch=getchar(); while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar(); while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); return f?x:-x; } int n,m,head[MAXN],cnt,a[MAXN][MAXN],zz[MAXN]; int to[MAXN<<1],nxt[MAXN<<1],tot; LL g[MAXN][MAXN],ans,now; bool use[MAXN]; void out(LL x){ if(!x) return ; out(x/10);putchar('0'+x%10); } inline void add(int bg,int ed){ to[++cnt]=ed,nxt[cnt]=head[bg],head[bg]=cnt; } void dfs(int x,int f){ int u;LL sum; for(register int i=1;i<=tot;i++) g[x][i]=1; for(register int i=head[x];i;i=nxt[i]){ u=to[i];if(u==f) continue;dfs(u,x); for(register int j=1;j<=tot;j++){sum=0; for(register int k=1;k<=tot;k++) if(a[zz[j]][zz[k]]) sum+=g[u][k]; g[x][j]*=sum; } } } int main(){ n=rd(),m=rd();int x,y; for(register int i=1;i<=m;i++){ x=rd(),y=rd(); a[x][y]=a[y][x]=1; } for(register int i=1;i<n;i++){ x=rd(),y=rd(); add(x,y),add(y,x); }int num; for(register int S=1;S<1<<n;S++){ tot=0;num=0;now=0; for(register int i=1,T=S;T;T>>=1,i++) if(T&1) num++,zz[++tot]=i; num=n-num;dfs(1,0);for(register int i=1;i<=tot;i++) now+=g[1][i]; if(num&1) ans-=now;else ans+=now; } if(!ans) putchar('0');else out(ans); return 0; }