Luogu3349 [ZJOI2016]小星星
题面:洛谷
解析
容斥原理好题。称原图点集为A,树的点集为B,那么题目中所求即为B中元素与A中元素一一对应且边合法的方案数,考虑容斥,设\(g(S)(S\subseteq A)\)表示B仅与S中元素对应的方案数(不一定对应完全),那么有:
\[ans=\sum_{S} (-1)^{ n-\vert{S}\vert }g(S)\]
现在考虑如何计算\(g(S)\),考虑动态规划,令1为树的根节点,设\(f(i,j)\)表示以i为根的子树,ii与j的方案数,那么转移顺着原图中的边与树上的边即可,有:
\[f(i,j)=\prod_{v=i.son} \sum_{k=j.to} f(v,k)\]
代码
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#define N 20
#define LL long long
using namespace std;
inline int In(){
char c=getchar(); int x=0,ft=1;
for(;c<'0'||c>'9';c=getchar()) if(c=='-') ft=-1;
for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
return x*ft;
}
int n,m,S0,q[N],h1[N],h2[N],tot1=1,tot2=0,qc=0;
LL f[N][N],sum,ans;
struct E{ int to,nex; }e1[N*N],e2[N<<1];
inline void Add1(int u,int v){
e1[++tot1]=(E){v,h1[u]}; h1[u]=tot1;
e1[++tot1]=(E){u,h1[v]}; h1[v]=tot1;
}
inline void Add2(int u,int v){
e2[++tot2]=(E){v,h2[u]}; h2[u]=tot2;
e2[++tot2]=(E){u,h2[v]}; h2[v]=tot2;
}
void dfs(int u,int pre){
for(int i=h2[u];i;i=e2[i].nex)
if(e2[i].to!=pre) dfs(e2[i].to,u);
for(int i=1;i<=qc;++i){
f[u][q[i]]=1;
for(int j=h2[u],v;j;j=e2[j].nex){
v=e2[j].to; if(v==pre) continue; sum=0;
for(int k=h1[q[i]],v_q;k;k=e1[k].nex){
v_q=e1[k].to; sum+=f[v][v_q];
}
f[u][q[i]]*=sum;
if(!f[u][q[i]]) break;
}
}
}
int main(){
n=In(); m=In(); S0=(1<<n)-1;
for(int i=1;i<=m;++i) Add1(In(),In());
for(int i=1;i<n;++i) Add2(In(),In());
for(int S=1;S<=S0;++S){
qc=0; memset(f,0,sizeof(f));
for(int i=1;i<=n;++i)
if(S&(1<<(i-1))) q[++qc]=i;
dfs(1,-1); sum=0;
for(int i=1;i<=qc;++i) sum+=f[1][q[i]];
ans=ans+((n-qc)&1?-sum:sum);
}
printf("%lld\n",ans);
return 0;
}