[BZOJ 3167][HEOI 2013]SAO
[BZOJ 3167][HEOI 2013]SAO
題意
對一個長度為 \(n\) 的排列作出 \(n-1\) 種限制, 每種限制形如 "\(x\) 在 \(y\) 之前" 或 "\(x\) 在 \(y\) 之後". 且保證任意兩點之間都有直接或間接的限制關系. 求方案數量.
\(n\le 1000\).
題解
Sword Art Online還行
拖了很久終於想起這題了...
首先我們發現這個限制關系是樹狀的, 那麽我們嘗試用子樹來定義狀態. 設 \(dp_{i,j}\) 表示以 \(i\) 為根的子樹中的元素組成的合法排列且 \(i\) 在升序下排在第 \(j\)
保序合並比較簡單, 枚舉一個排列在合並後的排列中占據哪些位置就可以了. 顯然這是一個簡單的組合數.
為了符合限制條件, 我們需要把在最終排列中排在當前根之前的結點和之後的結點區別對待. 枚舉子樹中有 \(j\) 個點排在根之前. 然後將限制分兩類討論:
若限制是子結點先於根節點, 那麽設根節點為 \(r\), 子結點為 \(s\), 有:
\[ dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=1}^jdp_{s,k} \]若限制是根結點先於子結點, 那麽有:
\[ dp_{r,i+j}={i+j-1\choose i-1}{\text{size}_r+\text{size}_s-i-j\choose \text{size}_s-j}dp_{r,i}\sum_{k=j+1}^{\text{size}_s}dp_{s,k} \]
其中等式右側(包括 \(\text{size}_r\) )都是合並 \(s\) 所在子樹信息前的信息.
不難發現這個東西是個 \(O(n^3)\) 的爆炸復雜度. 但是轉移式最後的和式是個前/後綴和的形式, 預處理一下就可以均攤 \(O(1)\) 得到那個和式的值了.
於是復雜度就變成 \(O(n^2)\)
以及這題復雜度的分析, 我們直接看轉移式會感覺它單次轉移是 \(O(n^2)\) 的, 實際上 \(i\) 和 \(j\) 的枚舉範圍分別只有 \(\text{size}_r\) 和 \(\text{size}_s\). 總枚舉量就是 "當前要合並的子樹大小" 與 "已經合並過的兄弟子樹大小之和" 的積. 那麽對於 \(r\) 的轉移枚舉量其實就等於以 \(r\) 為LCA的點對數量. 那麽整棵樹的總枚舉量就是 \(O(n^2)\) 了.
參考代碼
然而排列是 \([0,n)\) 的...
#include <bits/stdc++.h>
const int MAXN=1010;
const int MOD=1e9+7;
struct Edge{
int from;
int to;
int typ;
Edge* next;
};
Edge E[MAXN*2];
Edge* head[MAXN];
Edge* top=E;
int n;
int tmp[MAXN];
int inv[MAXN];
int fact[MAXN];
int size[MAXN];
int pf[MAXN][MAXN];
int sf[MAXN][MAXN];
int dp[MAXN][MAXN];
int C(int,int);
void DFS(int,int);
int Pow(int,int,int);
void Insert(int,int,int);
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(dp,0,sizeof(dp));
memset(pf,0,sizeof(pf));
memset(sf,0,sizeof(sf));
memset(head,0,sizeof(head));
top=E;
scanf("%d",&n);
fact[0]=1;
for(int i=1;i<=n;i++)
fact[i]=1ll*fact[i-1]*i%MOD;
inv[n]=Pow(fact[n],MOD-2,MOD);
for(int i=n;i>=1;i--)
inv[i-1]=1ll*inv[i]*i%MOD;
for(int i=1;i<n;i++){
int a,b;
char op;
scanf("%d %c%d",&a,&op,&b);
Insert(a,b,op=='>');
Insert(b,a,op=='<');
}
DFS(0,-1);
printf("%d\n",sf[0][1]);
}
return 0;
}
void DFS(int root,int prt){
size[root]=1;
dp[root][1]=1;
for(Edge* k=head[root];k!=NULL;k=k->next){
if(k->to!=prt){
DFS(k->to,root);
memset(tmp,0,sizeof(tmp));
if(k->typ){
for(int i=1;i<=size[root];i++){
for(int j=1;j<=size[k->to];j++){
tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*pf[k->to][j])%MOD;
}
}
}
else{
for(int i=1;i<=size[root];i++){
for(int j=0;j<size[k->to];j++){
tmp[i+j]=(tmp[i+j]+1ll*C(i+j-1,i-1)*C(size[root]+size[k->to]-i-j,size[k->to]-j)%MOD*dp[root][i]%MOD*sf[k->to][j+1])%MOD;
}
}
}
size[root]+=size[k->to];
memcpy(dp[root],tmp,sizeof(tmp));
}
}
for(int i=1;i<=size[root];i++)
pf[root][i]=(dp[root][i]+pf[root][i-1])%MOD;
for(int i=size[root];i>=1;i--)
sf[root][i]=(dp[root][i]+sf[root][i+1])%MOD;
}
inline void Insert(int from,int to,int typ){
top->from=from;
top->to=to;
top->typ=typ;
top->next=head[from];
head[from]=top++;
}
inline int C(int n,int m){
return n<0||m<0||n<m?0:1ll*fact[n]*inv[m]%MOD*inv[n-m]%MOD;
}
inline int Pow(int a,int n,int p){
int ans=1;
while(n>0){
if(n&1)
ans=1ll*a*ans%p;
a=1ll*a*a%p;
n>>=1;
}
return ans;
}
[BZOJ 3167][HEOI 2013]SAO