1. 程式人生 > >5151 (區間dp +組合計數)

5151 (區間dp +組合計數)

這道這道區間DP,我也開始覺得其實區間DP是一種應用型的思想,做這類題目一個重要的點是在於題目情景的把握,這道題的一個情景就是數學的排列組合問題. 首先應用根據小區間推出大區間的思路,我們可以先固定一個位置k,k位置是最後做的位置,那麼我們要算出在這種情況下符合的方法數,假如k是頭或者尾,那不用說, 此時的方法為dp[i+1][j]或者是dp[i][j-1]; 假如k是有鄰居的,那我們就要考慮鄰居的顏色,如果顏色不同,那麼方法便是0,否則的話,可以根據排列組合的方法,方法數自然等於dp[i][k-1]*dp[k+1][j]*X; 這個X便是考你對於情景的理解,i-k-1以及k+1-j這兩個區間之內已經是各自相對有序做好的位置,那在形成一組的時候我們也要保證相對有序 這裡我們用組合的思想進行分析: 首先總共有j-i個位置(因為不考慮k位置),那麼我們有兩個各自相對有序的陣列要合二為一,問最終有多少種不同的序列。 這邊我一開始的想法便是用插空法,但一直過不了,小的資料過的去,大的資料過不去,我猜應該是在大資料的時候資料溢位了,因為插空的思想準沒錯。 所以這邊退而求其次,還記得組合當中有所謂的什麼元素法還有位置法,你可以一開始固定元素去插,也可以固定位置去排。 這邊我們選擇固定位置,一開始為k-i個元素選擇好位置,那另外一組數的位置自然也固定了。 這邊也牽扯到一個十分重要的思想:便是我們在考慮兩個變數的時候,可能會由於各自運算的時間可能剛好符合給的限定時間,假如合起來的話會超過。 這時候我們不妨尋找兩個變數之間的關係,從而只做一個數,從關係當中,推出另外一個數,這是一種十分有效,並且能夠突破思維的方法。(藍橋杯的教訓) 給出狀態轉移方程:dp[i][j]=(dp[i][j]+dp[i][k-1]*dp[k+1][j]*c[j-i][k-i]) --------------------- 本文來自 jason_star 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/u013611908/article/details/43193651?utm_source=

前面的基本他已經分析的非常清楚了,我補充一下對那個組合數的理解:

區間[i,j]中,我們已經知道了區間[i,k-1],[k+1,j],他們都是已經計算好的,有序的。現在我們考慮進行區間合併,那麼現在的問題就是有多少種不同的合併方法,總人數有j-i+1,但是要去掉第k個,所以還有j-i個,[i,k-1]區間有k-i個人,我們表面上需要進行一次排列才能夠計算出方法數,但是問題是[i,k-1]已經是有序的了,我們不要在考慮排列問題。

囉嗦了半天,它就是[i,k-1]內部類似一個自動排列機一樣,只要你把人分給它,那它就擁有了不同的排列方法,所以我們只要考慮將j-i個人,分k-i個出來,就可以解決問題了(對於後面的j-k個,只要前面的確定了,那麼它也就確定了)。

總結:這類dp+組合數學的問題,不能只考慮dp或組合數學的內容,就像上面的分析,合併區間的方法數,看似應該是排列數,但是加上我們的dp定義以後,我們只要求出組合數就行了。

程式碼:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
const int inf=0x3f3f3f3f;
typedef long long ll;
ll C[120][102],A[120];
const int mod=1e9+7;
int n;
ll dp[120][120];
int a[120];
void init()
{
    A[0] = 1;
    for(int i=1; i<=100; i++)
        A[i] = (A[i-1] * i)%mod;
    C[0][0] = 1;
    for(int i=1; i<=100; i++)
    {
        C[i][0] = 1;
        C[i][i]=1;
        for(int j=1; j<=i; j++)
            C[i][j] = (C[i-1][j-1] + C[i-1][j])%mod;
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif
    init();
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            dp[i][i]=1;
            if(i<n)
            {
                dp[i][i+1]=2;
            }
        }   
        for(int l=2;l<n;l++)
        {
            for(int i=1;i<=n-l;i++)
            {
                int j=l+i;
                for(int k=i;k<=j;k++)
                {
                    if(i==k)//兩邊直接放
                    {
                        dp[i][j]+=dp[i+1][j];
                    }
                    else if(j==k)
                    {
                        dp[i][j]=(dp[i][j]+dp[i][j-1])%mod;
                    }
                    else
                    {
                        if(a[k-1]==a[k+1])
                        {
                            dp[i][j]=(dp[i][j]+dp[i][k-1]*dp[k+1][j]%mod*C[j-i][k-i]%mod)%mod;
                        }
                    }
                }
            }
        }
        printf("%lld\n",dp[1][n]);
    }
    return 0;
}