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

題目鏈接:
洛谷
LOJ

題目大意:
給出一個圖,給出一棵樹,你需要把樹上的點映射到圖上,兩個點不能映射到同一個點。
要求若兩個點在樹上有一條邊連着,那麼映射到的點在原圖上也要有一條邊,求方案數。

考慮dp。
如果直接按照兩個點不能映射到同一個點的限制來做,發現子狀態比較難設計(好像O(3nn)O(3^n*n)珂以做,但是會爆)
因此先考慮珂以有兩個點映射到同一個點的情況qwq
dp[i][j]dp[i][j]表示以ii爲根,且ii對應原圖中的jj的方案數。
O(n3)O(n^3)轉移:
初始化:dp[x][i]=1dp[x][i]=1,因爲若xx的子樹中只有xx,那麼xx珂以對應到原圖任意一個點。
對於dfs遍歷到的節點xxxx的孩子vv,枚舉xx映射到的點jjvv映射到的點kk
jjkk在原圖中連了邊,那麼把dp[v][k]dp[v][k]加到sumsum中,最後根據乘法原理,讓dp[x][j]dp[x][j]乘上sumsum
然後發現這樣子dp出來的結果有重複qwq,兩個點珂以映射到同一個點
比如有3個點,1映射到2,2映射到2,3映射到1。
觀察發現,這種情況就有一個點(3)沒有被映射到qwq
所以減去這種有一個點沒有映射到的情況就珂以了。
……
…………
………………
真的是介個樣子嗎?
考慮有兩個點沒被映射到的情況:
還是舉3個點的例子,1映射到2,2映射到2,3也映射到2。
發現這樣在1沒有映射到的時候會減掉一次,3沒有映射到的時候也會減掉一次,所以再加回來就珂以了。
因此用容斥,如果有奇數個點沒有映射到,就減掉,偶數個點沒有映射到就加上qwq
時間複雜度O(2nn3)O(2^nn^3)

毒瘤代碼

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章