1. 程式人生 > >P4099 [HEOI2013]SAO

P4099 [HEOI2013]SAO

數據 edi 細節 %s color -- 判斷 ref 我們

傳送門

n 個關卡有 n-1 個限制

所以這些限制構成一顆樹

考慮樹形DP

對一顆子樹單獨考慮

考慮有多少種順序

設 f [ i ] 表示節點 i 的子樹的總方案數

考慮兒子節點如何與父節點合並

發現父子之間有限制條件,所以 f 多加一維 f [ i ] [ j ] 表示節點 i 在子樹中排第 j 時的方案數

子樹合並時就可以看成兩個序列合並

比如像這樣(x和v是的父節點):

  { ,,x,, } + { 。。v 。。。} = { ,。。,v 。x , 。。, }

上圖就是 f [ x ] [ 3 ] 與 f [ v ] [ 3 ] 的一種合並方案 --> f [ x ] [ 7 ]

然後考慮方案數的增長

兒子的一部分合並到父節點左邊,另一部分合並到父節點右邊

對於父節點 x 和兒子節點 v

我們枚舉合並後的父節點的排名 k ,枚舉合並前的父節點排名 j,枚舉兒子分離的中間點 o

兒子左半部分合並到父親的方案有 C[ k-1 ] [ k-j ]

右部分合並父親的方案有 C[ sz[x] - k ] [ sz[x]-sz[v]-j ](sz[ x ]此時已經包括sz [ v ])

然後可以得到完整的轉移方程(不考慮父子關系的情況):  

技術分享圖片(sz是節點大小)

但這是O(n^3)的轉移

優化十分顯然,f [ v ] [ o ] 可以提出來用前綴和一起算,然後就是O(n^2)

然鵝我們還要考慮到父子間的限制...

那麽如果 父節點要在子節點後 --> k-j ≥ o ≥ 1

反之 sz[v] ≥ o > k-j

初始 f [ x ] [ 1 ] = 1 (合並前所有節點的子樹只有它自己)

註意有多組數據,記得清空數組

實現看代碼,要註意細節

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace
std; typedef unsigned long long ll; inline int read() { int x=0; char ch=getchar(); while(ch<0||ch>9) ch=getchar(); while(ch>=0&&ch<=9) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x; } const int N=2e3+7,mo=1e9+7; int fir[N],from[N<<1],to[N<<1],cnt; inline void add(int &a,int &b) { from[++cnt]=fir[a]; fir[a]=cnt; to[cnt]=b; } inline ll fk(ll x) { return x>=mo ? x-mo : x; }//這樣取模會快一點 int n; bool mp[N][N];//存父子間的大小關系 ll C[N][N];//組合數 void pre()//預處理組合數 { C[0][0]=1; for(int i=1;i<=n;i++) for(int j=0;j<=i;j++) C[i][j]=fk(C[i-1][j-1]+C[i-1][j]); } int sz[N]; ll f[N][N],g[N][N];//g是f的前綴和 void dfs(int x,int fa) { for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa) continue; dfs(v,x); sz[x]+=sz[v]; for(int j=sz[x];j;j--)//註意j從大到小轉移,先更新大的再更新小的 { int L=min(sz[x]-sz[v],j); ll sum=0; for(int k=1;k<=L;k++) { if(mp[x][v])//判斷大小關系 { int l=j-k,r=sz[v];//o的範圍 if(l<r)//註意邊界 { ll t=( C[j-1][j-k] * C[sz[x]-j][sz[x]-sz[v]-k] )%mo; sum=fk(sum+( (f[x][k]*t)%mo * fk(g[v][r]+mo-g[v][l]) )%mo); } } else { int r=min(j-k,sz[v]); ll t=( C[j-1][j-k] * C[sz[x]-j][sz[x]-sz[v]-k] )%mo; sum=fk(sum+( (f[x][k]*t)%mo * g[v][r] )%mo); } } f[x][j]=sum; } } for(int i=1;i<=sz[x];i++) g[x][i]=fk(g[x][i-1]+f[x][i]);//計算前綴和 } inline void clr() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=g[i][j]=mp[i][j]=0; for(int i=1;i<=n;i++) sz[i]=f[i][1]=1/*註意*/,fir[i]=0; cnt=0;//cnt別忘了清空 } int T; int main() { T=read(); while(T--) { int a,b; char c[5]; n=read(); pre(); clr(); for(int i=1;i<n;i++) { a=read(); scanf("%s",c); b=read(); a++; b++; add(a,b); add(b,a); if(c[0]==<) mp[a][b]=1; else mp[b][a]=1; } dfs(1,1); ll ans=0; for(int i=1;i<=n;i++) ans=fk(ans+f[1][i]); printf("%lld\n",ans); } return 0; }

P4099 [HEOI2013]SAO