【uoj185】【ZJOI2016T3】小星星 树形dp&容斥

       考场上对这道题目一点思路都没有。。一直以为状态和顺序有关然后就gg了QAQ。然而这就是能用子集dp的!/(ㄒoㄒ)/~~都想到用容斥了哭。。但是dp状态搞不出来QAQ。

       令f[i][j][S]表示以i所在的子树(不妨令1为根节点)中与图中编号集合为S的点一一对应,且i与j对应的时候的方案数,然后就可以大力转移了。这样是O(N^3*3^N)的,拿的分好像和暴力差不多再见。(听说可以用f[i][S]表示状态然后大力转移为O(N3^N)但是窝不会QAQ)

       考虑容斥,枚举T表示所有点和图中集合为T的点对应的情况(不保证一一对应),然后答案就是和U(全集)一一对应的情况,容斥一下就好了。枚举是O(2^N)的,然后树形dp为O(N^3),因此时间复杂度为O(N^3*2^N),这样就可以过了。(在uoj上面卡了一下常数就过了)。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;

int n,m,cnt,tot,fst[21],pnt[42],nxt[42],bin[21],a[21]; bool mp[21][21]; ll dp[21][21];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
void add(int x,int y){
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
void dfs(int x,int fa){
	int p,i,j,y; ll tmp;
	for (p=fst[x]; p; p=nxt[p])
		if (pnt[p]!=fa) dfs(pnt[p],x);
	for (i=1; i<=cnt; i++){
		dp[x][i]=1;
		for (p=fst[x]; p; p=nxt[p]){
			tmp=0; y=pnt[p]; if (y==fa) continue;
			for (j=1; j<=cnt; j++)
				if (mp[a[i]][a[j]]) tmp+=dp[y][j];
			dp[x][i]*=tmp; if (!dp[x][i]) break;
		}
	}
}
int main(){
	n=read(); m=read(); int i,x,y; ll ans=0,sum;
	bin[0]=1; for (i=1; i<=n; i++) bin[i]=bin[i-1]<<1;
	for (i=1; i<=m; i++){
		x=read(); y=read();
		mp[x][y]=mp[y][x]=1;
	}
	for (i=1; i<n; i++){
		x=read(); y=read(); add(x,y); add(y,x);
	}
	for (i=1; i<bin[n]; i++){
		sum=cnt=0;
		for (x=1; x<=n; x++) if (i&bin[x-1]) a[++cnt]=x;
		dfs(1,0);
		for (x=1; x<=cnt; x++) sum+=dp[1][x];
		if ((n^cnt)&1) ans-=sum; else ans+=sum;
	}
	printf("%lld\n",ans);
	return 0;
}


by lych

2016.3.25

你可能感兴趣的:(动态规划,容斥原理,树形DP)