You Are the One

題意: 
有一個序列,每個ai代表一個屌絲值k,ai的不開心值是 
k*(i-1),但是你可以用一個小房子改變序列的順序。

狀態轉移方程是考慮一個人插到k個人之後,可以得到的最優解。一個人是子狀態,小房子可以放多個人是一個全局的狀態,不應該考慮到總的狀態,所以要從子狀態開始。

dp[i][j]表示i到j最小的不開心值,對於第i個人,他可能有兩種狀態,一是沒有放到房子裏,二是放到房子裏。 
放到房子裏,假設插到k個人後面,那麼就可以得出兩個子狀態dp[i+1][i+k-1]和dp[i+k][j],這裏還要注意dp[i][j]表示從i到j這段區間不考慮i前面的大區間。因爲i插到了第k位,所以屌絲值要a[i]*(k-1),dp[i+k][j]由於要前面排上了k位,所以要加上(s[j]-s[k+i-1])*k)

#include <iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define inf 1<<27;
#define min(a,b) a>b?b:a
using namespace std;
int dp[105][105];
int sum[105],val[105];
int main()
{
    int i,j,l,k;
    int T,n;
    scanf("%d",&T);
    for(int t=1;t<=T;t++)
    {
        scanf("%d",&n);
        sum[0]=0;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&val[i]);
            sum[i]=sum[i-1]+val[i];
        }
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
        {
            for(j=i+1;j<=n;j++)
            {
                dp[i][j]=inf;
            }
        }
        for(l=1;l<=n-1;l++)
        {
            for(i=1;i<=n-1;i++)
            {
                j=i+l;
                for(k=i;k<=j;k++)
                {
                    dp[i][j]=min(dp[i][j],val[i]*(k-i)+(k-i+1)*(sum[j]-sum[k])+dp[i+1][k]+dp[k+1][j]);
                }
            }
        }

        printf("Case #%d: %d\n",t,dp[1][n]);
    }

    return 0;
}


遞歸理解:

題意說的是1 - n個屌絲男,然後通過一個小黑屋也就是個棧可以改變他們的順序,每個人有個屌絲值,然後如果這個人排第k個,則他的不高興值爲(k - 1) * D
問所有人的不高興值之和最小是多少
先分析這個問題,對於區間1 - n,第一個人可能是第一個上場,也可能是第n個上場,其實對於第一個人,他有可能是1 - n中第k個上場(1 <= k <= n)
爲什麼他可以是第1 - n中任意一個上場呢,因爲
比如說這樣
原始隊伍:1 2 3 4 5
棧底-->2 3 4 5<--棧頂 舞臺 :1
1直接上舞臺,剩下的四個可以直接上,也可以先進小黑屋再上,這時候1就是第一個上了
棧底-->1 2 3 4 5<--棧頂 舞臺:null
棧底-->1 2 3<--棧頂 舞臺:4 5
比如這兩種情況,1就是最後上場的
還有在1 - n之間的
隊伍:3 4 5
棧底-->1 2<--棧頂 舞臺:null
然後這時先讓1 2出棧上舞臺
隊伍:3 4 5
棧底--><--棧頂 舞臺:2 1
然後3 4 5再上
棧底--><--棧頂 舞臺:2 1 3 4 5
這時候1就是排第二了,其他位置也可以相似地得到
發現了子問題
因爲1可以是第1 - n中任意一個上舞臺,不妨設他是第k個上的,然後發現在考慮這個問題的時候考慮哪種情況都是可以分成三部分
標號爲1的屌絲本身,上舞臺之後排在在1之前的k - 1個,在1之後的n - k個
然後再看在1之前上舞臺的k - 1個屌絲,他們可以以同樣的分析方法進行更小範圍的獨立分析,在1之後的n - k個也可以
1在第k個上舞臺時,他的不高興值爲a[i] * (k - 1),那麼只要知道他之前的所有屌絲的不高興值之和的最小值sumpre,和他之後的所有屌絲的不高興值之和的最小值之和sumnext,最後1 - n上的最小值就是
a[i] * (k - 1) +sumpre + sumnext
sumpre和sumnext又可以以同樣的方式遞歸分析下去
直到精確到不可再分的區間長度爲一的個人
定義函數int solve(i, j)函數返回區間[i, j]上的題目要求的最小值,那麼
solve(i, j) = min(solve(i + 1, i + k - 1) + solve(i + k, j) + a[i] * (k - 1) + (sum[j] - sum[i + k - 1]) * k) (1 <= k <= j - i + 1)
區間[i, j]
i i + 1, i + 2, i + 3, ..........,i + k - 1 i + k ..........i + k + 1.........j
第k個上 之前的k - 1個 之後的 j - i - k + 1個
因爲在第i之後的那個區間[i + k, j]在分析的時候是內部獨立考慮的,因爲在考慮這個子問題的時候肯定不知道它外面的父問題是什麼
所以在[i + k, j]上的最優解只是獨立考慮[i + k, j]的最小值,在綜合到[i, j]這個區間時由於[i, i + k - 1]這k個都在他們前面所以
[i + k, j]這個最優解還要再加上D(i + k) * k + D(i + k + 1) * k + ...... + D(j) * k, D(x)爲第x個屌絲的屌絲值
如果用前綴和來表示的話就是前j個的屌絲值和sum[j],前i + k - 1個的屌絲值和sum[i + k - 1]
D(i + k) * k + D(i + k + 1) * k + ...... + D(j) * k = (sum[j] - sum[i + k - 1]) * k
邊界
發現在k = 1時
solve(i + 1, i) + solve(i + 1, j) + a[i] * (1 - 1) + (sum[j] - sum[i]) * 1
這個時候也就是第i個是排在[i, j]這個區間的第一個,可見他之前沒有別人,從i + 1到j全在他後面
而且有個沒有意義的solve(i + 1, i)明顯要讓這個值有意義只有讓它等於0
solve(i + 1, j) + (sum[j] - sum[i])
在k = j - i + 1時
solve(i + 1, j) + solve(j + 1, j) + a[i] * (j - i) + (sum[j] - sum[j]) * (j - i + 1);
這個時候i排在[i, j]這個區間最後一個,從i + 1到j全在他前面
這時候又出現了solve(j + 1, j)沒有意義,也是明顯他要等於0,而且因爲都在i前面,所以[i + 1, j]上的所有屌絲都不用補(sum[j] - sum[i + k - 1]) * k) 了,當然爲0
solve(i + 1, j) + a[i] * (j - i)
開數組直接遞推,很快
形象來看像是這麼個過程,就是父問題去覆蓋子問題的過程,每次的覆蓋範圍都在擴大,最終擴大到n,將所有的子問題的
解都覆蓋了,也就是所有子問題向上構成了最終問題的解

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn = 100 + 10, INF = 0x3f3f3f3f;
int n, a[maxn], sum[maxn], dp[maxn][maxn];
int solve(int i, int j)
{
    if (i > j)
        return 0;
    int ans = dp[i][j];
    if (ans != -1)
        return ans;
    ans = INF;
    for (int k = 1; k <= j - i + 1; k++)
    {
        ans = min(ans, solve(i + 1, i + k - 1) + solve(i + k, j) + a[i] * (k - 1) + (sum[j] - sum[i + k - 1]) * k);
    }
    return dp[i][j] = ans;
}
int main()
{
    int T;
    scanf("%d", &T);
    for (int t = 1; t <= T; t++)
    {
        scanf("%d", &n);
        sum[0] = 0;
        for (int i = 1; i <= n; i++)
        {
             scanf("%d", &a[i]);
             sum[i] = sum[i - 1] + a[i];
        }
            memset(dp, -1, sizeof(dp));
            printf("Case #%d: %d\n", t, solve(1, n));
    }
    return 0;
}




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