洛谷P3205 [HNOI2011]合唱隊 DP
阿新 • • 發佈:2018-07-19
() 依次 開始 原則 cin 問題 lin sync problem
然後我們就可以按照先枚舉長度,再枚舉起點的區間\(DP\)的套路來轉移了(^▽^)
上代碼
原題鏈接點這裏
今天在課上聽到了這個題,聽完後覺得對於一道\(DP\)題目來說,好的狀態定義就意味著一切啊!
來看題:
題目描述 為了在即將到來的晚會上有更好的演出效果,作為AAA合唱隊負責人的小A需要將合唱隊的人根據他們的身高排出一個隊形。假定合唱隊一共N個人,第i個人的身高為Hi米(1000<=Hi<=2000),並已知任何兩個人的身高都不同。假定最終排出的隊形是A 個人站成一排,為了簡化問題,小A想出了如下排隊的方式:他讓所有的人先按任意順序站成一個初始隊形,然後從左到右按以下原則依次將每個人插入最終棑排出的隊形中: -第一個人直接插入空的當前隊形中。 -對從第二個人開始的每個人,如果他比前面那個人高(H較大),那麽將他插入當前隊形的最右邊。如果他比前面那個人矮(H較小),那麽將他插入當前隊形的最左邊。 當N個人全部插入當前隊形後便獲得最終排出的隊形。 例如,有6個人站成一個初始隊形,身高依次為1850、1900、1700、1650、1800和1750, 那麽小A會按以下步驟獲得最終排出的隊形: 1850 1850 , 1900 因為 1900 > 1850 1700, 1850, 1900 因為 1700 < 1900 1650 . 1700, 1850, 1900 因為 1650 < 1700 1650 , 1700, 1850, 1900, 1800 因為 1800 > 1650 1750, 1650, 1700,1850, 1900, 1800 因為 1750 < 1800 因此,最終排出的隊形是 1750,1650,1700,1850, 1900,1800 小A心中有一個理想隊形,他想知道多少種初始隊形可以獲得理想的隊形 輸出格式: 註意要mod19650827 說明 30%的數據:n<=100 100%的數據:n<=1000
首先,不難發現這樣的隊列一定有一個性質,對於每次完成加人操作後的隊列,它一定是最終隊列的一個子區間。於是我們就可以用區間DP來搞這道題了。
下面的這個狀態定義,非常的巧妙(不可能的,我這一輩子都是不可能想出來的):
令\(h[i]\)為最終隊列第i個人的身高,\(f[l][r][0], f[l][r][1]\)分別為對於區間\([l,r]\)最後一次加人是在左邊,和在右邊的方案數,不難yy出轉移方程如下:
\(f[l][r][0] = f[l+1][r][0]*(h[l+1]>h[l])+f[l+1][r][1]*(h[r]>h[l])\)
\(f[l][r][1] = f[l][r-1][0]*(h[l]<h[r])+f[l][r-1][1]*(h[r-1]<h[r])\)
然後我們就可以按照先枚舉長度,再枚舉起點的區間\(DP\)的套路來轉移了(^▽^)
上代碼
#include <iostream> using namespace std; const int N = 1005, mod = 19650827; int n, h[N], f[N][N][2]; //0代表左邊 1代表右邊 int main() { ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); //讀入優化 cin >> n; for(int i = 1; i <= n; i++) cin >> h[i], f[i][i][0] = 1; //初始化 for(int k = 2; k <= n; k++) //枚舉長度 for(int i = 1; i+k-1 <= n; i++) { //枚舉起點 int l = i, r = i+k-1; f[l][r][0] = (f[l+1][r][0]*(h[l+1]>h[l])+f[l+1][r][1]*(h[r]>h[l]))%mod; //狀態轉移 f[l][r][1] = (f[l][r-1][0]*(h[l]<h[r])+f[l][r-1][1]*(h[r-1]<h[r]))%mod; } cout << (f[1][n][0]+f[1][n][1])%mod; //最終答案為最後一次加人在左邊和在右邊的和 return 0; }
洛谷P3205 [HNOI2011]合唱隊 DP