题目大意:给一张图和该图的一棵生成树,求可能的编号方案数
题解:一共要满足两个限制:1.树中的一个点对应图中一个点,且一一对应 2.树中两点有边的,图中两点也对应有边
因为有两个限制,一种喜闻乐见的做法就是先搞♂掉其中一些限制,最后对一个限制容斥
2n
枚举标号集合
然后用f[i][j]表示i节点标号为j的方案数,
O(n3)
dp求解
记 ret=∑f[1][i] , ans+=(−1)|S|⋅ret
需要一点常数优化才可以过
我的收获:容斥大法!
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 21
int n,m,cnt;
int p[N],a[N];
ll dp[N][N],ans,sum;
bool mp[N][N];
vector<int> E[N];
int read()
{
int x=0; char ch=getchar();
while (ch<'0' || ch>'9') ch=getchar();
while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
return x;
}
void dfs(int x,int fa){
for(int i=0;i<E[x].size();i++)
if(E[x][i]!=fa) dfs(E[x][i],x);
for(int i=1;i<=cnt;i++){
dp[x][i]=1;
for(int s=0;s<E[x].size();s++){
int v=E[x][s];
if(v==fa) continue;
ll temp=0;for(int j=1;j<=cnt;j++) if(mp[a[i]][a[j]]) temp+=dp[v][j];
dp[x][i]*=temp;if(!dp[x][i]) break;
}
}
}
void work()
{
for(int i=0;i<p[n+1];i++){
sum=cnt=0;
for(int x=1;x<=n;x++) if(i&p[x]) a[++cnt]=x;
dfs(1,0);
for(int x=1;x<=cnt;x++) sum+=dp[1][x];
if((n^cnt)&1) ans-=sum;else ans+=sum;
}
printf("%lld\n",ans);
}
void init()
{
n=read();m=read();int x,y;
for(int i=1;i<=m;i++){
x=read();y=read();
mp[x][y]=mp[y][x]=1;
}
for(int i=1;i<n;i++) x=read(),y=read(),E[x].push_back(y),E[y].push_back(x);
for(int i=1;i<=20;i++) p[i]=1<<i-1;
}
int main()
{
init();
work();
return 0;
}