1. 程式人生 > 其它 >【洛谷P3349】[ZJOI2016]小星星

【洛谷P3349】[ZJOI2016]小星星

【洛谷傳送門】
借鑑了 \(\mathtt{wind\_whisper}\) 的思路,他的【部落格傳送門】(不過我的程式碼寫的比他好看多了)

題解

先不提及優化,一開始本人並沒有設計出樸素 DP。(或許有些浮躁?)

設計 DP

看到資料範圍還有題目裡面玄學的對應關係,可以想到狀壓,用一維表示已被對應的節點狀態。
考慮對於每一個轉移的父子點對 \(u,v\),由於轉移的條件就是有連邊,所以 \(u,v\) 對應的點在原圖中一定有連邊,轉移的時候把 \(u\) 當前子樹和 \(v\) 子樹的狀態合併(也就是邏輯或),就可以順利轉移。
因此,設計 \(dp(u,stat,p)\) 表示 \(u\) 節點對應的節點是 \(p\)

\(u\) 子樹內對應狀態為 \(stat\)

優化 DP

可以用容斥優化,但是比較難。(先挖坑)

  • 最簡單的想法,基於 DP 本身的狀態,每個子樹對應的狀態裡 \(1\) 的個數一定等於子樹大小,預處理出來可以省去大量列舉。
  • 其次,列舉子樹的時候類比樹形揹包,每次轉移完 DP 之後再 siz[u]+=siz[v]
  • 基本就這些,還可以有一點點無聊的優化,比如在 DP 值為 \(0\) 的時候直接跳出迴圈。

吸氧能夠卡過,最大點約 \(850ms\)

程式碼

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 20;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c>='0'&&c<='9')) ch=c,c=getchar();
	while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
int n,m,head[N],ecnt=-1;
bool mp[N][N];
inline void init_edge(){memset(head,-1,sizeof(head)),ecnt=-1;}
struct edge
{
	int nxt,to;	
}a[N<<1];
inline void add_edge(int x,int y)
{
	a[++ecnt]=(edge){head[x],y};
	head[x]=ecnt;
}
ll dp[N][1<<18][N],siz[N];
int num[N][1<<18],cnt[N];
void dfs(int u,int fa)
{
	siz[u]=1;
	for(int i=1;i<=n;i++) dp[u][1<<(i-1)][i]=1LL;
	//初始化,u對應任何一個點方案都為1
	for(int i=head[u];~i;i=a[i].nxt)
	{
		int v=a[i].to;
		if(v==fa) continue; 
		dfs(v,u);
		for(int j=1;j<=cnt[siz[u]];j++)	
			for(int k=1;k<=cnt[siz[v]];k++)
			{
				int stat1=num[siz[u]][j];
				int stat2=num[siz[v]][k];
				if(stat1&stat2) continue;
				for(int p=1;p<=n;p++)
					if((1<<(p-1))&stat2)
					{
						if(!dp[v][stat2][p]) continue;
						for(int q=1;q<=n;q++)
						{
							if(mp[q][p]&& ((1<<(q-1))&stat1) )
							dp[u][stat1|stat2][q]+=dp[u][stat1][q]*dp[v][stat2][p];
						}
					}
			}
		siz[u]+=siz[v];
	}
}
int main()
{
	init_edge();
	n=read(),m=read();
	for(int i=1;i<=m;i++)	
	{
		int u=read(),v=read();
		mp[u][v]=mp[v][u]=1;
	}
	for(int i=1;i<n;i++)	
	{
		int u=read(),v=read();
		add_edge(u,v),add_edge(v,u);
	}
	for(int i=0;i<1<<n;i++)	
	{
		int tcnt=0;
		for(int j=1;j<=n;j++)	
			if((1<<(j-1))&i) tcnt++;
		num[tcnt][++cnt[tcnt]]=i;
	}
	dfs(1,-1);
	ll ans=0ll;
	for(int i=1;i<=n;i++)		
		ans+=dp[1][(1<<n)-1][i];
	printf("%lld\n",ans);
	return 0;
}