[loj#2091] [ZJOI2016] 小星星
题意简述
给定一个 \(n\) 个点 \(m\) 条边的无向图,再给定一棵 \(n\) 个点的树。
要把树中的点与无向图中的点一一对应,要求树上有连边的点所对应的点在图上也有连边。
问有多少种对应方式。
\(n\leq 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]=\prod\limits_{fa[v]=i} (\sum\limits_{j与k在原图中有边} f[v][k])\)
显然 \(g[s]=\sum\limits_{i\in s}f[root][i]\)
容斥很经典,\(ans=\sum(-1)^{n-|s|} \times 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\n",ans);
return 0;
}
既然选择了远方,便只顾风雨兼程