传送门
题意简述:给一张图和一棵树(点数都为
n
≤
17
n \le17
n≤17),问有多少种给树的标号方法方法使得图中去掉多余的边之后和树一模一样。
思路:
容斥好题啊。
考虑
f
i
,
j
f_{i,j}
fi,j表示把
i
i
i对应成原图中的点
j
j
j这棵子树的对应方案数。
然后转移就枚举儿子看能不能转,如果可以就更新当前答案。
但是这样会有多个树中的节点对应到同一个图中的节点上。
于是我们用
2
n
2^n
2n的时间去枚举可以对应的原图的点集合然后容斥即可。
代码:
#include<bits/stdc++.h>
#define ri register int
using namespace std;
const int N=18;
typedef long long ll;
int n,m,tot,a[N];
ll f[N][N],ans=0,sum;
bool trans[N][N];
vector<int>e[N];
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
inline void dfs(int p,int fa){
for(ri i=0,v;i<e[p].size();++i)if((v=e[p][i])^fa)dfs(v,p);
for(ri i=1;i<=tot;++i){
f[p][i]=1;
for(ri j=0,v;j<e[p].size();++j){
if((v=e[p][j])==fa)continue;
ll tmp=0;
for(ri k=1;k<=tot;++k)if(trans[a[i]][a[k]])tmp+=f[v][k];
f[p][i]*=tmp;
}
}
}
int main(){
n=read(),m=read();
for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),trans[u][v]=trans[v][u]=1;
for(ri i=1,u,v;i<n;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
for(ri i=1;i<(1<<n);++i){
tot=0,sum=0;
for(ri j=1;j<=n;++j)if((i>>(j-1))&1)a[++tot]=j;
dfs(1,0);
for(ri j=1;j<=tot;++j)sum+=f[1][j];
ans+=sum*((n-tot)&1?-1:1);
}
cout<<ans;
return 0;
}