洛谷 P4099 - SAO
emmm... tzc 說是好題,那肯定是好題,因為 tzc 英語帶師!
洛谷題目頁面傳送門
給定一個基圖為樹的有向圖,求它的拓撲序個數。答案對 \(10^9+7\) 取模。本題多測。
\(n\in[1,1000],T\in[1,5]\)。
考慮樹形 DP。很自然的想到 \(dp_{i,j}\) 表示子樹 \(i\) 中節點 \(i\) 在拓撲序列中的序數為 \(j\) 的拓撲序列數。
那麼在轉移的時候,要把一個節點的所有兒子都考慮進去,這個極不好控制,是指數級的。不難發現,兒子與兒子之間沒有直接聯絡,只是 \(i\) 與兒子們各有一個聯絡。於是可以將兒子一個一個算入轉移中,而非同時轉移,這樣能使時間複雜度得以控制在多項式級別。具體地,大概就加個一維 \(dp_{i,j,k}\)
目標:\(\sum\limits_{i=1}^ndp_{1,|son_1|,i}\);邊界:\(dp_{i,0,1}=1\)。
接下來考慮如何轉移。當前要計算 \(dp_{i,j}\),我們設 \(h=dp_{i,j},f=dp_{i,j-1},g=dp_{son_{i,j},\left|son_{son_{i,j}}\right|}\)
考慮列舉 \(h\) 中在 \(i\) 前面有幾個 \(g\) 中的點。那麼此時 \(i\) 在 \(f\) 中的排名已經確定了,用在 \(h\) 中的排名減去前面在 \(g\) 中的點數即可。那麼 \(son_{i,j}\) 在 \(g\) 中的排名呢?它需要在 \(i\) 前面,於是在 \(1\)
\[h_i=\sum_{j=1}^{sz_g}f_{i-j}\sum_{k=1}^jg_k\binom{i-1}j\binom{sz_h-i}{sz_g-j} \]
類似地可以得到後者的方程(比較噁心一點):
\[h_i=\sum_{j=1}^{sz_g}f_{i+j-sz_g}\sum_{k=sz_g-j+1}^{sz_g}g_k\binom{sz_h-i}j\binom{i-1}{sz_g-j} \]
此時總時間複雜度是 \(\mathrm O\!\left(n^4\right)\) 的(平方狀態,平方轉移)。考慮優化。
顯然,兩個方程中的第二個 \(\sum\) 可以用字首和優化掉,此時三方,繼續優化。然後定睛一看:寫成字首和形式之後,展開組合數,發現一派常數一派關於 \(j\) 一派關於 \(i-j\),那不就是個卷積嗎?!我當時害怕極了,我不會 FFT 呀!而且即使優化了,也是平方對數,還要乘個 \(T\),也是過不去的樣子。
然後發現這個三方的複雜度很虛偽,它實際上是 \(\mathrm O\!\left(\sum\limits_{i=1}^n\sum\limits_{j=1}^{|son_i|}sz_{son_{i,j}}\sum\limits_{k=1}^{j-1}sz_{son_{i,k}}\right)\),很跑不滿三方。然後拿鏈、菊花、蒲公英、毛毛蟲、隨機樹都試一遍,發現沒有一個被卡掉的。事實上它是平方的。下面歸納證明 \(\mathrm T(n)\leq n^2\)。
- 顯然 \(\mathrm T(0)=0\)。
- 假設 \(\forall i\in[0,x),\mathrm T(i)\leq i^2\)。設大小為 \(x\) 的樹的樹根的兒子子樹大小分別為 \(a_{1\sim m}\),則顯然有 \(\mathrm T(x)=\sum\limits_{i=1}^m\mathrm T(a_i)+\sum\limits_{i=1}^ma_i\sum\limits_{j=1}^{i-1}a_j\)。根據假設有 \(\mathrm T(x)\leq \sum\limits_{i=1}^ma_i^2+\sum\limits_{i=1}^ma_i\sum\limits_{j=1}^{i-1}a_j=\sum\limits_{i=1}^ma_i\sum\limits_{j=1}^ia_j\leq x^2\)。
綜上,得證。
程式碼(要很多次取模,常數比較大,要開 \(\mathrm O_2\)):
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
const int mod=1000000007;
const int N=1000;
int n;
int comb[N+1][N+1];
vector<pair<int,bool> > nei[N+1];
int dp[N+1][N+2];
int nw[N+1];
int sz[N+1];
void dfs(int x=1,int fa=0){
sz[x]=1;
dp[x][1]=1;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i].X;bool z=nei[x][i].Y;
if(y==fa)continue;
dfs(y,x);
sz[x]+=sz[y];
if(z){
for(int j=1;j<=sz[y];j++)(dp[y][j]+=dp[y][j-1])%=mod;
for(int j=1;j<=sz[x];j++)nw[j]=0;
for(int j=1;j<=sz[x];j++)for(int k=0;k<=sz[y]&&k<j;k++)
(nw[j]+=1ll*dp[x][j-k]*dp[y][k]%mod*comb[j-1][k]%mod*comb[sz[x]-j][sz[y]-k]%mod)%=mod;
for(int j=1;j<=sz[x];j++)dp[x][j]=nw[j];
}
else{
for(int j=sz[y];j;j--)(dp[y][j]+=dp[y][j+1])%=mod;
for(int j=1;j<=sz[x];j++)nw[j]=0;
for(int j=1;j<=sz[x];j++)for(int k=0;k<=sz[y]&&k<=sz[x]-j;k++)
(nw[j]+=1ll*dp[x][j+k-sz[y]]*dp[y][sz[y]-k+1]%mod*comb[sz[x]-j][k]%mod*comb[j-1][sz[y]-k]%mod)%=mod;
for(int j=1;j<=sz[x];j++)dp[x][j]=nw[j];
}
}
}
void mian(){
cin>>n;
for(int i=1;i<=n;i++)nei[i].clear();
memset(dp,0,sizeof(dp));
for(int i=1;i<n;i++){
int x,y;char z;
cin>>x>>z>>y;
x++,y++;
if(z=='<')nei[x].pb(mp(y,0)),nei[y].pb(mp(x,1));
else nei[x].pb(mp(y,1)),nei[y].pb(mp(x,0));
}
dfs();
int ans=0;
for(int i=1;i<=n;i++)(ans+=dp[1][i])%=mod;
cout<<ans<<"\n";
}
int main(){
comb[0][0]=1;
for(int i=1;i<=N;i++)for(int j=0;j<=i;j++)comb[i][j]=((j?comb[i-1][j-1]:0)+(j<i?comb[i-1][j]:0))%mod;
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}