[07/18NOIP模擬測試5]超級樹


鬼能想到的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 }
考時垃圾,考后牛逼。。。有什么用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM