1. 程式人生 > >洛谷P3205 [HNOI2011]合唱隊 DP

洛谷P3205 [HNOI2011]合唱隊 DP

() 依次 開始 原則 cin 問題 lin sync problem

原題鏈接點這裏
今天在課上聽到了這個題,聽完後覺得對於一道\(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