[zjoi2016]小星星 (容斥+DP)

我们先用树形DP,求出选取集合S中的点,满足连通性的但是标号可重的方案数,贡献给F(i)(1\(\leq\)i\(\leq\)\(\mid S\mid\)),也就是我们要处理出F(i)代表取至多i个点的方案数。
然后容斥一下就求出恰好选i个点的方案数。
卡一下常就能过了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define int long long
const int N=141000;
int dp[18][18],f[N],c[18],tot;
int cnt,head[18];
int n,m,S;
bool vis[18][18];
struct edge{
	int to,nxt;
}e[N];
void add_edge(int u,int v){
	cnt++;
	e[cnt].nxt=head[u];
	e[cnt].to=v;
	head[u]=cnt;
}
void dfs(int u,int f,int now){
//	cout<<u<<endl;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==f)continue;
		dfs(v,u,now);
	}
	for(int j=1;j<=tot;j++){
		dp[u][c[j]]=1;
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(v==f)continue;
			int num=0;
			for(int g=1;g<=tot;g++){
				if(c[j]==c[g])continue;
				if(vis[c[j]][c[g]]==0)continue;
				num+=dp[v][c[g]];
			}
			dp[u][c[j]]*=num;
		}
	}
}
int work(int x){
	int tmp=0;
	while(x)tmp+=x&1,x>>=1;
	return tmp;
}
int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';;ch=getchar();}
	return sum*f;
}
signed main(){
	n=read();m=read();
	S=(1<<n)-1;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		vis[u][v]=vis[v][u]=1;
	}
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		add_edge(u,v);add_edge(v,u);
	}
	for(int i=1;i<=S;i++){
	//	cout<<i<<" "<<S<<endl;
		tot=0;
		for(int j=0;j<n;j++)if(i&(1<<j))c[++tot]=j+1;
		dfs(1,0,i);
		for(int j=1;j<=tot;j++)f[i]+=dp[1][c[j]];
	}
//	cout<<"sjdhfsjfwsd"<<endl;
	int ans=0;
	for(int i=S;i>=0;i--)
		ans+=((n-work(i))%2==0?1:-1)*f[i];
	printf("%lld",ans);
	return 0;
}
posted @ 2019-01-15 16:13  Xu-daxia  阅读(206)  评论(0编辑  收藏  举报