[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;
}