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;
}




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