1. 程式人生 > 其它 >題解 超級樹

題解 超級樹

傳送門

這轉移是人想的?
這個轉移是真的想不出來……有空找個拓寬思路的dp題單康康?(咕)

考場上又一次以為是組合數,好在後來反應過來是個dp但是不會寫,最後打表拿了15pts
考慮轉移,分析深度+1會對方案數造成什麼影響
\(dp[i]\)表示深度為i的超級樹的路徑數
深度已知,則含有的點數可求,考慮用組合數處理增加的連邊方案
然後發現因為向子樹連邊時子樹每個點曾連出的邊也會影響方案數,就不會轉移了

然後考完試著拓展了一下這個思路:
\(dp[i]\)表示深度為i的超級樹的路徑數,再令\(cnt[i][j]\)為在i-超級樹中以一個深度為j的點為起點的路徑數
首先深度+1, \(dp[i+1]+=2*dp[i]\)


然後考慮新增的這個根節點帶來的方案數
以新根節點為始,長度為1方案數(就是直連根節點和已有點):\(dp[i+1]+=2^i-1\)
考慮跨根節點連邊:列舉兩邊始末點深度即可
然而不跨根節點,從同一子樹向根節點連出兩條邊炸掉了
這裡有個問題:只知兩個以深度為j, k的點為起點的路徑數,無法在把這兩條路徑連通後仍保證經過節點不重複
如果不允許從同一子樹向根節點連出兩條邊貌似就能用這種方法了,不過時間複雜度並不會更優
所以考慮處理「從新根節點向點連邊」炸掉了,還是因為只考慮每個點連出的邊的方案數,而實際上具體方案間不滿足直接合並

然後正解:
那就考慮邊,令\(dp[i][j]\)為在i-超級樹中選出j條深度不同且滿足經過點不重複條件的路徑的方案數
理解狀態設計花了我20min


注意這裡在轉移時可以連通兩條邊,使它們連為一條,所以邊數每次至多減1,k次轉移至多減k,所以l和r列舉到k即可
轉移基本一樣,列舉左右子樹所選的邊的型別,注意「點連本身也算一條路徑」和「這裡每條邊都是不同的」即可
為了卡常,令\(num=dp[i][l]*dp[i][r]\)
不加邊:\(dp[i+1][l+r]+=num\)
根節點自身連邊(其它邊選法任意):\(dp[i+1][l+r+1]+=num\)
根節點向兩子樹連邊:\(dp[i+1][l+r]+=2*num*(l+r)\)
兩子樹間跨根節點連邊:\(dp[i+1][l+r-1]+=2*num*l*r\)
同一子樹內通過根節點連邊:\(dp[i+1][l+r-1]+=2*num*(C(l, 2)+C(r, 2))\)

\(dp[i+1][l+r-1]+=num*(l*(l-1)+r*(r-1))\)
我這裡之前一直沒理解對 居然現在才發現 暴躁.jpg
我一直理解的是可選的連邊少了一條,但實際上是組合數式子拆開了……
當晚upd: 之前的理解方法也是對的……就是可選邊少了一條,所以是\(l*(l-1)\),這裡因為處理l和r時相當於考慮了順序就不用乘2了
yysy,這個解法其實和考慮點解法差不多吧(霧

再說優化:
邊數每次至多減1,顯然\(l+r \geqslant k\)就沒用了
而且考慮列舉到深度為i時,還有\((k-i+1)\)輪,所以這時\(l \geqslant k-i+1\)就沒用了,和上一條同時用上更優,可以1800ms

而且這題卡細節頗多,考慮對1取模,一定等於0
而這裡\(dp[1][1]=1\),直接輸出會炸,所以輸出時記得再取一次模

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 310
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

int k;
ll mod, dp[N][N];
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	ll num;
	
	scanf("%d%lld", &k, &mod);
	dp[1][0]=dp[1][1]=1;
	for (int i=1,lim; i<k; ++i) 
		for (int l=0,lim=k-i+1; l<=k-i+1; ++l,--lim) 
			for (int r=0; r<=lim; ++r) {
				//cout<<i<<' '<<l<<' '<<r<<endl;
				num=dp[i][l]*dp[i][r]%mod;
				md(dp[i+1][l+r], num);
				md(dp[i+1][l+r+1], num);
				md(dp[i+1][l+r], 2ll*num*(l+r)%mod);
				md(dp[i+1][l+r-1], 2ll*num*l*r%mod);
				md(dp[i+1][l+r-1], num*(l*(l-1)+r*(r-1))%mod);
			}
	printf("%lld\n", dp[k][1]%mod);

	return 0;
}