洛谷3349 & LOJ2091 ZJOI2016 小星星 容斥+dp

探讨了将一棵树上的点映射到图上的问题,确保相连的点在图上也有边相连。通过动态规划解决,考虑了点可能映射到同一位置的情况,并使用容斥原理修正结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:
洛谷
LOJ

题目大意:
给出一个图,给出一棵树,你需要把树上的点映射到图上,两个点不能映射到同一个点。
要求若两个点在树上有一条边连着,那么映射到的点在原图上也要有一条边,求方案数。

考虑dp。
如果直接按照两个点不能映射到同一个点的限制来做,发现子状态比较难设计(好像 O ( 3 n ∗ n ) O(3^n*n) O(3nn)珂以做,但是会爆)
因此先考虑珂以有两个点映射到同一个点的情况qwq
d p [ i ] [ j ] dp[i][j] dp[i][j]表示以 i i i为根,且 i i i对应原图中的 j j j的方案数。
O ( n 3 ) O(n^3) O(n3)转移:
初始化: d p [ x ] [ i ] = 1 dp[x][i]=1 dp[x][i]=1,因为若 x x x的子树中只有 x x x,那么 x x x珂以对应到原图任意一个点。
对于dfs遍历到的节点 x x x x x x的孩子 v v v,枚举 x x x映射到的点 j j j v v v映射到的点 k k k
j j j k k k在原图中连了边,那么把 d p [ v ] [ k ] dp[v][k] dp[v][k]加到 s u m sum sum中,最后根据乘法原理,让 d p [ x ] [ j ] dp[x][j] dp[x][j]乘上 s u m sum sum
然后发现这样子dp出来的结果有重复qwq,两个点珂以映射到同一个点
比如有3个点,1映射到2,2映射到2,3映射到1。
观察发现,这种情况就有一个点(3)没有被映射到qwq
所以减去这种有一个点没有映射到的情况就珂以了。
……
…………
………………
真的是介个样子吗?
考虑有两个点没被映射到的情况:
还是举3个点的例子,1映射到2,2映射到2,3也映射到2。
发现这样在1没有映射到的时候会减掉一次,3没有映射到的时候也会减掉一次,所以再加回来就珂以了。
因此用容斥,如果有奇数个点没有映射到,就减掉,偶数个点没有映射到就加上qwq
时间复杂度 O ( 2 n n 3 ) O(2^nn^3) O(2nn3)

毒瘤代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<vector>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=(x<<1)+(x<<3)+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(const int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=18;
const int MaxE=145;
int n,m,cnt,head[Size];
struct Edge {
	int v,next;
} w[MaxE<<1];
void AddEdge(int u,int v) {
	w[++cnt].v=v;
	w[cnt].next=head[u];
	head[u]=cnt;
}
bool G[Size][Size],del[Size];
ll dp[Size][Size];		//以i为根,且i对应原图中的j号点的方案数 
void dfs(int x,int fa) {
	for(re i=1; i<=n; i++) {
		dp[x][i]=1;
	}
	for(int i=head[x]; i; i=w[i].next) {
		int nxt=w[i].v;
		if(nxt!=fa) {
			dfs(nxt,x);
			for(re j=1; j<=n; j++) {
				if(del[j])	continue;
				ll sum=0;
				for(re k=1; k<=n; k++) {
					if(del[k] || !G[j][k])	continue;
					sum+=dp[nxt][k];
				}
				dp[x][j]*=sum;
			}
		}
	}
}
inline void oper(re x) {
	memset(del,0,sizeof(del));
	re cnt=1;
	while(x) {
		if(x&1)	del[cnt]=true;
		x>>=1;
		cnt++;
	}
}
int main() {
	n=read();
	m=read();
	for(re i=1; i<=m; i++) {
		int u=read();
		int v=read();
		G[u][v]=G[v][u]=true;
	}
	for(re i=1; i<n; i++) {
		int u=read();
		int v=read();
		AddEdge(u,v);
		AddEdge(v,u);
	}
	ll ans=0;
	for(re i=0; i<(1<<n); i++) {
		oper(i);
		dfs(1,0);
		ll sum=0;
		for(re j=1; j<=n; j++) {
			sum+=dp[1][j];
		}
		if(__builtin_popcount(i)&1) {
			ans-=sum;
		} else {
			ans+=sum;
		}
	}
	printf("%lld",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值