uoj185

http://uoj.ac/problem/185

首先考虑一个很假的树形 DP. dp[u][p] 表示考虑了以 u 为根的
这个子树, 并且根映射到原图的 p . 这个显然可以 O(n3) 转移,
是会有不同的点可能映射到同一个点. 于是考虑容斥.
求出 dp(S) 表示映射的点集至多为 S 时的答案, 然后就可以
O(2n*n3) 做了.

#include<cstdio>
#define FOR(i,s,t) for(register int i=s;i<=t;++i)
typedef long long ll;
struct edge{
	int to;
	edge *nxt;
}e[666],*las[666],*et=e;
int map[666][666];
ll f[666][666];
int a[666];
inline void add(int x,int y){
	*++et=(edge){y,las[x]};las[x]=et;
}
int n,m,x,y,up;
ll sum,ans;
inline void dp(int now,int fa){
	register ll tmp;
	for(register edge *it=las[now];it;it=it->nxt)
		if(it->to!=fa)
			dp(it->to,now);
	for(register int i=1;i<=a[0];++i){
		f[now][i]=1;
		for(register edge *it=las[now];it;it=it->nxt)
			if(it->to!=fa){
				tmp=0;
				for(register int j=1;j<=a[0];++j)
					if(map[a[i]][a[j]])
						tmp+=f[it->to][j];
				f[now][i]=f[now][i]*tmp;
				if(!f[now][i])
					break;
			}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	FOR(i,1,m){
		scanf("%d%d",&x,&y);
		map[x][y]=map[y][x]=1;
	}
	FOR(i,2,n){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	up=(1<<n)-1;
	for(register int i=1;i<=up;++i){
		a[0]=0;
		FOR(j,0,16)
			if(i&(1<<j))
				a[++a[0]]=j+1;
		sum=0;
		dp(1,0);
		for(register int j=1;j<=a[0];++j)
			sum=sum+f[1][j];
		ans=ans+(((n^a[0])&1)?-sum:sum);
	}
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/Stump/p/8228093.html