1. 程式人生 > 其它 >洛谷 P6383 -『MdOI R2』Resurrection(DP)

洛谷 P6383 -『MdOI R2』Resurrection(DP)

猜結論+樹形 dp

洛谷題面傳送門

高速公路上正是補 blog 的時候,難道不是嗎/doge,難不成逆在高速公路上寫題/jy

首先形成的圖顯然是連通圖並且有 \(n-1\) 條邊。故形成的圖是一棵樹。

我們考慮什麼樣的樹能夠得到。考慮以 \(n\) 為根,由於每個點的編號都小於其父親這個條件的存在,我們每次斷開一條邊時,兩個連通塊中編號最大的點肯定是這兩個連通塊中深度最淺的節點。而顯然,對於一條邊 \((u,v)\),如果 \(u\)\(v\) 的父親,那麼斷開 \((u,v)\)\(v\) 肯定是所在連通塊中深度最淺的節點,也就是說我們要為每個點 \(x\) 找一個祖先 \(p_x\),滿足斷開 \(x\)

與其父親的邊時,\(p_x\) 為其父親所在連通塊中深度最淺的節點。

考慮什麼樣的序列 \(p\) 符合要求。打個表發現一條鏈的情況答案是卡特蘭數(cartesian number bushi)。而卡特蘭數剛好是由 \(n\) 個左括號和 \(n\) 個右括號組成的括號序列的數量,而括號序列中每對括號肯定是不能相交的——即,要麼相離,要麼互相包含。因此我們猜測一組 \(p\) 符合條件,當且僅當不存在兩個 \(x,y\) 滿足 \(p_x,p_y,x,y\) 依次存在祖先關係。事實上這個結論是正確的可惜我不會證。這樣就可以 DP 了。考慮 \(dp_{i,j}\) 表示確定了 \(i\) 祖先

(注意,這裡與傳統的 DP 不同,因為傳統的 DP 一般都假設子樹內的狀態已經確定,而這題是假設祖先的狀態已經確定)的 \(p\),目前 \(i\) 還有 \(j\) 個祖先可以選擇,有多少個欽定 \(i\) 子樹內點的 \(p\) 的方法,考慮如何轉移,我們列舉 \(p_i\) 是目前可行的點中,從下往上數的第幾個,設為 \(c\),那麼這樣在欽定 \(i\) 的兒子時會 ban 掉 \(c-1\) 個祖先,同時又會為 \(u\) 的兒子新增一個符合要求的祖先——\(u\),因此我們有 \(dp_{u,j}=\sum\limits_{c=1}^j\prod\limits_{v\in\text{son}(u)}dp_{v,j-c+2}\)
。這樣直接轉移是三方的,無法通過。不過注意到這個 \(\sum\) 可以用字首和優化掉,具體來說我們設 \(dp_{u,j}=dp_{u,j-1}+\prod\limits_{v\in\text{son}(u)}dp_{v,j+1}\),這樣記憶化搜尋一下複雜度即可達到平方。

為什麼會有個 freopen 呢?因為這是場 mns 的賽題……

const int MAXN=3000;
const int MOD=998244353;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dp[MAXN+5][MAXN+5];
int calc(int x,int f,int k){
	if(~dp[x][k]) return dp[x][k];dp[x][k]=0;
	if(k>1) dp[x][k]=calc(x,f,k-1);int res=1;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;
		res=1ll*res*calc(y,x,k+1)%MOD;
	} dp[x][k]=(dp[x][k]+res)%MOD;
	return dp[x][k];
}
int main(){
//	freopen("reflection.in","r",stdin);
//	freopen("reflection.out","w",stdout);
	scanf("%d",&n);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	memset(dp,-1,sizeof(dp));int res=1;
	for(int e=hd[n];e;e=nxt[e]){int y=to[e];res=1ll*res*calc(y,n,1)%MOD;}
	printf("%d\n",res);
	return 0;
}