[loj#2091] [ZJOI2016] 小星星

题意简述

给定一个 (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;
}
原文地址:https://www.cnblogs.com/lindalee/p/12404753.html