代码改变世界

SDUT_2146:最小子序列和

2015-04-20 22:47  星星之火✨🔥  阅读(485)  评论(0编辑  收藏  举报

题目描述

给你一个长为n(10<=n<=10000)的数组,数组中的每一个数大于等于1小于等于1000000。请你找出一个长为k(1<=k<=1000)的子序列。找序列时,假如第一个数找的是数组中的第i个位置的数,那么找第二个数时只能找数组中第i个位置后的数,依次找出k个数。使得第一个数*1+第二个数*2+...+第k个数*k的值最小。 

输入

 有多组(小于11组)测试数据,每组第一行输入n和k(用空格隔开),第二行输入n个数(数之间用空格隔开)。

输出

 请输出最小的和。

示例输入

15 5
5 4 3 2 1 1 2 3 4 5 5 4 3 2 1

示例输出

19

又是从标题就知道这是一道DP类的题。初学DP,这道题搞了有好几个小时Orz...

这次我变化下写作方式,先写下心得,毕竟耗了这莫长时间,没点心得不亏大了吗:-

心得体会:第一点——题目中给出的变量用来限界,状态方程中用其他的变量,防止引起混乱。

     第二点——写DP的核心部分时,可以先写伪码,使思路更加清晰。我就是这么做的,伪码一出,分分钟就写完DP了,至于伪码的写法,每个人都可以有自己的一套方法,这个可以在写程序的过程中不断精进。

下面开始分析:设dp(m, i) 表示长度为m 的序列取到第i 个数做尾数的最小子序列和,其中1 ≤ m ≤ i ≤ n。则有递推方程(注意变量个数是需要思考的,为什么这样列方程,这样列方程为什么是正确的都是需要思考的,为了防止文章写的又臭又长,就把这部分省略了,再说也不好说清楚:):

     m = 1 时,dp(1, i) = a[i], 1 ≤ i ≤ n。(注:数组a 保存了长度为n 个整个序列)

     2 ≤ m ≤ k 时,dp(m, i) = min{dp(m-1, j)} + a[i] * m, m-1 ≤ j < i ≤ n。

严格按照上面给出的递推方程的边界+伪码写程序,得:

#include<stdio.h> // Time Limit Exceeded: 1010MS
#define MAXN 10000
#define INF 1000000000000000 // 大于(1+1000)*1000/2*1000000即可,inf: infinite
long long int a[MAXN+10];
long long int dp[MAXN+10];
long long int temp[MAXN+10];
int main(void)
{
    int n, k;
    long long int min, ans; // ans: answer
    while(scanf("%d%d", &n, &k) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            dp[i] = temp[i] = a[i];
        }
        
        for(int m = 2; m <= k; m++)
        {
            min = INF;
            for(int i = m; i <= n; i++)
            {
                min = INF;
                for(int j = m-1; j < i; j++)
                {
                    if(min > temp[j])
                        min = temp[j];
                }
                dp[i] = min + a[i]*m;            
            }
            
            for(int i = m; i <= n; i++)
                temp[i] = dp[i];
        }   

        ans = INF;
        for(int i = k; i <= n; i++)
        {
            if(ans > dp[i])
                ans = dp[i];
        }
        printf("%lld\n", ans);
    }

    return 0;
}

正如上述程序注释中所写的,直接dp 超时了,因此还是得优化一下,我没有掌握过什么斜率/队列/四边形/树形这类听起来很牛的优化方法,因此只能找出最明显的可优化的部分给做掉了:

#include<stdio.h> // 90MS
#define MAXN 10000
#define INF 1000000000000000 // 大于(1+1000)*1000/2*1000000即可,inf: infinite
long long int a[MAXN+10];
long long int dp[MAXN+10];
long long int temp[MAXN+10];
int main(void)
{
    int n, k;
    long long int min, ans; // ans: answer
    while(scanf("%d%d", &n, &k) != EOF)
    {
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
            dp[i] = temp[i] = a[i];
        }
        
        for(int m = 2; m <= k; m++)
        {
            min = INF;
            for(int i = m; i <= n; i++)
            {
                if(min > temp[i-1])
                    min = temp[i-1];
                dp[i] = min + a[i]*m;
            }
            
            for(int i = m; i <= n; i++)
                temp[i] = dp[i];
        }   

        ans = INF;
        for(int i = k; i <= n; i++)
        {
            if(ans > dp[i])
                ans = dp[i];
        }
        printf("%lld\n", ans);
    }

    return 0;
}