鬼能想到的dp定義:dp[i][j]表示在一棵i級超級樹中,有j條路徑同時存在且這j條路徑沒有公共點時,可能的情況數
剛開始我也沒看懂,所以舉個例子
如一個2級的超級樹,父節點為1,左右兒子為2,3

(感謝Al_Ca貢獻的圖,但我感覺題目里給的這個帶編號更好一些。懶得把字去掉了將就着看吧)
(感謝xkl貢獻的截好的圖,但我斟酌了一下帶上樣例解釋你們是不是會更好理解啊~)
(我太挑剔啦,不用給我發圖啦,謝謝大家)
dp[2][1]=9,因為2級樹里的路徑一共有9條(樣例),顯然只有一條路徑的話肯定沒有公共點
dp[2][2]=7,(1,2)(1,3)(2,3)(2,13)(2,31)(3,12)(3,21),注意路徑方向不同就是不同的
//公告:原數有誤,感謝starsing邊罵我邊更正
dp[2][3]=1,只有(1單獨作為一條路徑,2單獨作為一條路徑,3單獨作為一條路徑,共3條)這一種可能。它們沒有公共點。
dp[2][4]=0,2級樹只有3個點,每條路徑至少1個點,所以4條路徑不可能沒有公共點
那么按照套路。。。遞推肝它!
如果你想要得到dp[i][j],該從什么轉移過來?
情況太多了,想不出來。。。
我們嘗試把dp[i][j]的情況設為已知,往后推別的
把兩棵i-1級的樹,配上一個根節點,合成一個i級的樹,合成過程中的方案?
枚舉左右子樹中各有多少路徑,左子樹j右子樹k,設sum=dp[i][j]*dp[i][k]
首先,想最簡單的玩意:與根節點無關,左右子樹保持原樣
那么整個圖中的路徑數並沒有變,左子樹還是j個,右子樹k個,只不過整個樹升級了,根據乘法原理
dp[i+1][j+k]+=dp[i][j]*dp[i][k],即dp[i+1][j+k]+=sum,就這樣累加方案數就好了
下一種情況,你隨便從左子樹里選一條邊,把這條邊的終點和父節點相連,相連之后的新路徑仍然與右子樹當中的任何一條路徑沒有交點
左邊的路徑增長了1,但是路徑的總數沒有變,還是(左子樹+根)有j條,右子樹有k條,從左子樹j個里挑出一個的方案數是j
dp[i+1][j+k]+=sum*j; ----(2-1-1)
然后我們既然可以把終點延伸到父節點,我們也可以把起點延伸到父節點
我們還是從左子樹中隨便挑路徑,把根節點連向它,同上,路徑延長而總數不變
dp[i+1][j+k]+=sum*j; ----(2-2-1)
你現在一直是在左子樹里挑邊,為什么不在右子樹里也挑呢?全部同理。不過是j變成了k
dp[i+1][j+k]+=sum*k; ----(2-1-2)
dp[i+1][j+k]+=sum*k; ----(2-1-2)
合並2打頭的這四個式子:dp[i+1][j+k]+=2*sum*(j+k);
考慮下一種情況:左子樹有j條右子樹有k條,可是父節點它本身還是一條啊
那么總路徑數就是(左j+右k+根1)=j+k+1
dp[i+1][j+k+1]+=sum;
再下一種情況:我們從左子樹中選出一條路徑,把它的終點連向父節點,在從根節點連向右子樹的一條路徑的起點
總路徑數是j+k-1,因為你把兩條合成了一條
還是舉例子,若左子樹有三條路徑ABC,右子樹3條abc。合成A和根再到a,現在的路徑就是A-a, B, C, b, c,為3+3-1=5條。
而選邊的方法數是j*k,這個是左連根再連右。同理還有右連根再連左,所以總數*2.
dp[i+1][j+k-1]+=sum*2*j*k;
最后一種情況,也是最惡心的:左子樹一條路徑終點連向父節點,再由父節點連向左子樹的另一條路徑的起點
證明可行性:因為dp含義中已經定義,這些路徑彼此沒有交點,那么從中任選兩條路徑后,與根節點按上述方法相連后仍然這一條路徑自身沒有公共點,與其他邊仍然沒有交點,滿足條件。
dp[i+1][j+k-1]+=sum*j*(j-1) (5-1)
因為是要在l條路徑中選出兩條不一樣的,且與順序有關不用除2
(你可以認為是選出的第一個路徑的起點作為合成路徑的起點,第二條路徑的終點作為合成路徑的終點,那么交換這兩條路徑所得到的合成路徑是不同的)
同理,從右邊選兩條:dp[i+1][j+k-1]+=sum*k*(k-1) (5-2)
合並一下:dp[i][j+k-1]+=sum*(j*(j-1)+k*(k-1));
好了,沒有其他情況了。5個藍色的式子就是全部的遞推式子,不重不漏。
初始狀態dp[1][0]=dp[1][1]=1;
最后的答案就是dp[n][1]了,即n級樹中全部的路徑數,沒有相交之類的限制(所以第二維選出一條路徑即可)
一定一定要多取模!
完事!
但是你A不了,對不對?
主要有兩種錯誤,稍講一下。
TLE :里面可能夾雜了WA,真的。
卡常,加法的取模優化,這還不夠。但是有必要!
你仔細想一下枚舉范圍,i肯定是到n了,那么j和k呢?
能給dp[m][q]貢獻答案的,是dp[m-1][?],問號如果是大於q+1,顯然就沒用了。即兩維之和不超過m+q
所以為了求出dp[n][1],那么兩維之和就不必超過n+1。所以對j的限制就是0~(n-i+2)
那么對k的限制就更緊了,0~(n-i+2-j)
還是不夠?前期的某些枚舉是無效的,如k=300,你會枚舉到dp[1][250]之類的
1級樹里怎么可能會有250條路徑嘛!
因此對枚舉的上屆再次限制,在滿足上一個條件的前提下,還要限制j.k<=2i-1
順便再提一下zkt的常數減半的優化:因為j和k同步的枚舉,顛倒它們的順序會累加完全一樣的答案
那么就干脆把sum加倍,強制k從j+1開始枚舉,特殊處理j==k即可。
WA 95了?(或者因為有T的點被顯示成了WA60之類)
友情贈送測試點“1 1”
我並沒有經歷這個問題,因為我輸出的時候順手取模了。
不要小瞧這5分!這很關鍵!要長記性!
然而。。。有一個最重要問題沒有解決,這個思路是怎么想到的?在題解的開頭我說它很難想到。
可是“如何想到”這很關鍵! 否則你只會看到別人的dp定義才會做,考試還是什么也不會。
思維這個東西很難講清楚,但我還是要冒險說一說。[不喜勿噴]
至少,i級樹從i-1級樹中推來的這個分解問題這個思路應該還是能夠想到的吧?
那么從這個角度出發分情況,應該是可以想到上述5種情況的。
在要用父節點拼接兩條路徑時,我們可以發現如果設的dp含義中如果不限制讓它沒有公共點,那么拼接起來無法計算!
這樣也許就會可以想到了吧。。。反正我沒想到。。。還要加油啊
接下來含義中已經定義了沒有交點。
我們觀察數據范圍,應該是n3左右的,最外層枚舉樹的等級沒什么問題,里面呢?
我們將樣例輸出2即245分解質因數(常用思路),發現出現了較大的質數,猜測這題不是簡單粗暴的相乘,而是某些東西相乘再求和
這樣我們就有了一個大體的框架(當然到這時候你還想不到限制枚舉范圍什么的)
1 for(int i=1;i<n;++I)//表示樹的等級 2 for(int j=1;j<=n;++j)//含義未知 3 for(int k=1;k<=n;++k)//含義未知 4 dp[i+1][?]+=dp[i-1][j]*dp[i-1][k]*(?);
現在我們只需要猜測出那兩個位置的含義是什么,式子中的問號就能鑿實了。
還有什么的限制是n級別的?說實在的我的第一反應是已經用在路徑中的點的數量。
但是我們可以發現每個點之間並不是平等的,它們的連邊關系不同,故籠統地用它們的數量來推是不現實的。
點不行,還能是啥?只能是邊了,而且沒有交點。說真的,不太好想。
但是邊之間的確彼此平等。。。枚舉范圍的確是n級別。。。
所以你就想到了。。。
好吧,我承認,有點牽強,但是的確有人能在考場上自己想出來,他們太強了,所以咱們更要練啊!
最后,給一個建議,看完了博客去打題之前,你最好再系統地理解一下,爭取碼代碼的時候不要再回來看公式。
自己根據含義想出來,自己推式子,理解消化,做題才有意義。
記住,你不是公式的搬運工。
最后送給你們幾個調試用小樣例(不取模)
1 1
2 9
3 245
4 126565
5 32054326261
1 #include<cstdio> 2 #include<iostream> 3 using namespace std; 4 #define int long long 5 #define sum (dp[i][l]*dp[i][r])%mod 6 int dp[301][602],n,k,mod; 7 inline void modd(long long &a){if(a>=mod)a-=mod;} 8 signed main(){ 9 scanf("%lld%lld",&n,&mod); 10 dp[1][0]=dp[1][1]=1; 11 for(int i=1;i<n;++i) for(int l=0;l<=min(n-i+2,i<=9?(1ll<<i)-1:n-i+2);++l) for(int r=0;r<=min(n-i+2-l,i<=9?(1ll<<i)-1:n-i+2);++r){ 12 modd(dp[i+1][l+r]+=sum);//直接轉移 13 modd(dp[i+1][l+r+1]+=sum);//只多了一個根自己是路徑 14 modd(dp[i+1][l+r]+=(sum<<1)*(l+r)%mod);//只把一條邊伸長到根節點,雙向 15 modd(dp[i+1][l+r-1]+=l*r%mod*(sum<<1)%mod);//左右通過根節點相連 16 modd(dp[i+1][l+r-1]+=sum*(l*(l-1)%mod+r*(r-1)%mod)%mod);//同側通過根節點相連 17 } 18 printf("%lld\n",dp[n][1]%mod); 19 }
