Treats for the Cows 區間DP

題目來源:http://poj.org/problem?id=3186

(http://www.fjutacm.com/Problem.jsp?pid=1389)


/**
  題目意思:
    約翰經常給產奶量高的奶牛發特殊津貼,於是很快奶牛們擁有了大筆不知該怎麼花的錢.
    爲此,約翰購置了N(1≤N≤2000)份美味的零食來賣給奶牛們.每天約翰售出一份零食.
    當然約翰希望這些零食全部售出後能得到最大的收益.這些零食有以下這些有趣的特性:
    零食按照1..N編號,它們被排成一列放在一個很長的盒子裏.盒子的兩端都有開口,約翰每
    天可以從盒子的任一端取出最外面的一個.
    與美酒與好吃的奶酪相似,這些零食儲存得越久就越好吃.當然,這樣約翰就可以把它們賣出更高的價錢.
    每份零食的初始價值不一定相同.約翰進貨時,第i份零食的初始價值爲Vi(1≤Vi≤1000).
    第i份零食如果在被買進後的第a天出售,則它的售價是vi×a.
    Vi的是從盒子頂端往下的第i份零食的初始價值.約翰告訴了你所有零食的初始價值,
    並希望你能幫他計算一下,在這些零食全被賣出後,他最多能得到多少錢.
**/
思路:這是一題區間動態規劃,但是又不是那麼複雜的區間動態規劃;
   這題要先找對狀態,不然很難進行下去,也許別的小夥伴有別的狀態,但是我找的狀態是dp[i][j]表示i~j範圍內取出倒數j-i+1個物品的最大值,比如dp[i][i]就是第取出倒數第一個爲a[i]可以獲得的值,dp[i-1][i]同理,表示倒數第一、二個爲a[i-1],a[i]的最大值。

   然後找對狀態了要找轉移方程:對於這題先要搞清楚我們是逆推,從區間小推向區間大,也就是用小的求大的,所以dp[i][j]是由比他小的部分加上一些東西組成的,此時我們可以發現,dp[i][j]這個區間我們只有兩種取法(題目給的是一個雙端隊列一樣的東西),也就是我們只能取a[j]或者a[i]這兩個數,而且這兩個數一定是在倒數j-i次取的,因爲之前只有j-i- 1(可能是a[i]或a[j]) 個數,而且都是倒數着的,這樣我們就可以發現,dp[i][j]=Max(dp[i+1][j]+a[i]*(n-(j-i)), dp[i][j-1]+a[j]*(n-(j-i)));看下面這個圖你就能理解他是怎麼步步緊逼的合併狀態的:


斜着的顏色分層的部分就表示i、j距離不同時由某些狀態過來的最大值,餘下的看看代碼吧!

ps:這裏這個距離是指帶代碼裏的l,因爲你要按照那個斜着的一層一層狀態轉移上去,否則就錯了;

#include<stdio.h>
#include<string.h>
const int N=2020;
int dp[N][N], a[N];
int Max(int a, int b){ return a>b?a:b; }
int main( ){
    int n;
    while(scanf("%d", &n)!=EOF){
        memset(dp, 0, sizeof(dp));
        for(register int i=1; i<=n; ++i)
            scanf("%d", a+i), dp[i][i]=a[i]*n;///設置爲當第i個數爲最後一個被取的數的情況;
        for(register int l=1; l<=n; ++l){///先跑i、j距離爲1的區間,然後順延下去纔可以保證不會出錯
            for(register int i=1; i+l<=n; ++i){
                int j=i+l;
                dp[i][j]=Max(dp[i+1][j]+a[i]*(n-l), dp[i][j-1]+a[j]*(n-l));
            }
        }
        printf("%d\n", dp[1][n]);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章