题意简述
给定一个 (n) 个点 (m) 条边的无向图,再给定一棵 (n) 个点的树。
要把树中的点与无向图中的点一一对应,要求树上有连边的点所对应的点在图上也有连边。
问有多少种对应方式。
(nleq 17)
想法
首先,我觉得这题应有 (bgm) 。
你 就是我的小星星
挂 在那天上放光明
我已经决定要爱你
就不会轻易放弃
哒哒哒~
言归正传!
先考虑树形 (dp) ,(f[i][j][s]) 表示 (i) 与原图中 (j) 对应时,以 (i) 为根的子树与 (s) 中的点对应的方案数((s) 为状压)
但这样过不去。
发现这里最麻烦的就是 (s) ,于是把它容斥掉。
设 (g[s]) 表示树上所有点只与 (s) 中的点对应的方案数。
在此(s) 下,(f[i][j]) 表示 (i) 与原图中 (j) 对应时,以 (i) 为根的子树与 (s) 中的点对应的方案数
有转移方程 (f[i][j]=prodlimits_{fa[v]=i} (sumlimits_{j与k在原图中有边} f[v][k]))
显然 (g[s]=sumlimits_{iin s}f[root][i])
容斥很经典,(ans=sum(-1)^{n-|s|} imes g[s])
总结
树上问题,先考虑树形 (dp) !别先想那些奇奇怪怪的做法!
神仙容斥……
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
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;
}
const int N = 18;
typedef long long ll;
int mp[N][N],con[N][N],n,m;
struct node{
int v;
node *nxt;
}pool[N*2],*h[N];
int cnt;
void addedge(int u,int v){
node *p=&pool[++cnt];
p->v=v;p->nxt=h[u];h[u]=p;
}
ll f[N][N];
int fa[N];
void dfs(int u){
for(int v=1;v<=n;v++)
if(con[u][v] && !fa[v]) {
fa[v]=u; addedge(u,v);
dfs(v);
}
}
int ok[N];
void cal(int u){
int v;
ll t;
for(node *p=h[u];p;p=p->nxt) cal(p->v);
for(int i=1;i<=n;i++){
if(ok[i]^1) { f[u][i]=0; continue; }
f[u][i]=1;
for(node *p=h[u];p;p=p->nxt){
v=p->v; t=0;
for(int j=1;j<=n;j++) if(mp[i][j]) t+=f[v][j];
f[u][i]*=t;
}
}
}
int main()
{
int u,v;
n=read(); m=read();
for(int i=0;i<m;i++){
u=read(); v=read();
mp[u][v]=mp[v][u]=1;
}
for(int i=1;i<n;i++) {
u=read(); v=read();
con[u][v]=con[v][u]=1;
}
fa[1]=-1; dfs(1);
int w=(1<<n),t;
ll ans=0,cur;
for(int s=1;s<w;s++){
t=0; cur=0;
for(int i=0;i<n;i++)
ok[i+1]=(1&(s>>i)),t+=ok[i+1];
cal(1);
for(int i=1;i<=n;i++) cur+=f[1][i];
ans+=cur*(((n-t)&1) ? (-1) : 1);
}
printf("%lld
",ans);
return 0;
}