动态规划+前缀和:Educational Codeforces Round 123 (Rated for Div. 2)C. Increase Subarray Sums

Educational Codeforces Round 123 (Rated for Div. 2)

C. Increase Subarray Sums

传送门:Problem - C - Codeforces

 

 

 

     题目大意就是:给你一个数组a,长度为n,再给你一个数为k,定义ans数组,求ans[i]的每个元素,i从0到n.ans[i]代表给数组中i个元素加上k后的最大连续子区间和。最大子区间长度可以是0,也就是一个数都不要。可以想:当i=0时,就是求序列的最大子区间和,当i!=0时,就不是了单纯的求最大子区间和再加上i*x了,因为如果最大子区间和长度是0,那么0个元素,就不能加上x,但是如果长度是1,元素是-1 ,加上x后可能大于0,所以就需要运用到动态规划,推出最优解.

    做法及其思路:这里有两个做法.

一、DP

    构建dp数组DP[i][j],i代表以最外层循环,循环每一个元素,也就是i:1->n,内层循环j代表,循环的是对于这个数结尾的子序列区间,加上j个x后的最优解,所以就是从j:1->n.。因为对于ans[0]来说,就只是单纯的求最大连续区间和。所以ans[0]放在外层循环求,每次用max函数更新。复习一下求最大连续区间和的做法,构建一个dp[]数组,对于每一个a[i],dp[i]=max(dp[i-1],0)+a[i];意思是如果dp[i-1]<0,那么前面的都不要,只要自己a[i],如果前面的大于0,就取千米爱你的dp[i-1]加上自身的a[i],然后每次算出来dp[i],都要ans都要比较更新一下ans=max(ans),但这里要写成二维的形式,dp[i][0],代表一个x都没加上。对于其他ans[i],i>0,在内层循环中寻找最大的dp[i][j]就是,内层循环的转移方程 int b,c。

  for int j 1to n

   b=max(dp[i-1][j],0)+a[i]

   c=max(dp[i-1][j-1],0)+a[i]+x;

   dp[i][j]=max(b,c);

    原理:对于一个dp[i][j]      他一定是从 dp[i-1],也就是上一层算出来的最大连续子区间,中选择 ①已经上一层算出来已经加上j个x后的最大连续子区间,再加上这层的a[i],如果上一层小于0,那么就不要上一层的最大连续子区间,所以有一个max(..,0)函数。②从上一层的已经加上j-1个x后,这一层加上x和a[i],同样上一层小于0,舍去。     虽然是这么描述的,但实际上上一层不一定加入了j个x或j-1个x,对于外层循环i,每次循环一个i,最多加入一个x,因为一个元素只能加上一个x,但也无关紧要,可以继续算下去,后面遍历到i等于...,一定会加上j个x,算出更好的答案,把前面ans[i]=dp[i][j],但这个i<j 实际上没加上足够的j个x,的ans[i]用max函数更新掉。

    状态转移方程代码:非常关键!可以好好结合代码和我的思路原理想一想.

    

for (int i = 1; i <= n; ++i)
        {
            dp[i][0] = max(dp[i - 1][0], 0) + a[i];
            ans[0] = max(dp[i][0], ans[0]);
            for (int j = 1; j <= n; ++j)
            {
                one = max(dp[i - 1][j],0) + a[i];
                two = max(dp[i - 1][j - 1],0) + a[i] + x;
                dp[i][j] = max(one, two);
                ans[j] = max(dp[i][j], ans[j]);
            }
        }

上总代码:

 1 #include <algorithm>
 2 #include <iostream>
 3 #include<cmath>
 4 #include<cstring>
 5 using namespace std;
 6 const int maxn = 5005;
 7 int read()
 8 {
 9     int f = 1;
10     int x = 0;
11     char c = getchar();
12     if (c > '9' || c < '0')
13     {
14         f = -1;
15         c = getchar();
16     }
17     while (c >= '0' && c <= '9')
18     {
19         x = (x << 3) + (x << 1) + c - '0';
20         c = getchar();
21     }
22     return f * x;
23 }
24 int a[maxn], dp[maxn][maxn], ans[maxn];
25 int main()
26 {
27     ios::sync_with_stdio(false);
28     cin.tie(0);
29     cout.tie(0);
30     int t;
31     t = read();
32     while (t--)
33     {
34         int n, x;
35         n = read(), x = read();
36         for (int i = 1; i <= n; ++i)a[i] = read();
37         for (int i = 0; i <= n; ++i)//重新初始化dp 和 ans数组
38         {
39             ans[i] = 0;
40             for (int j = 0; j <= n; ++j)
41             {
42                 dp[i][j] = 0;
43             }
44         }
45         int one, two;
46         for (int i = 1; i <= n; ++i)
47         {
48             dp[i][0] = max(dp[i - 1][0], 0) + a[i];
49             ans[0] = max(dp[i][0], ans[0]);
50             for (int j = 1; j <= n; ++j)
51             {
52                 one = max(dp[i - 1][j],0) + a[i];
53                 two = max(dp[i - 1][j - 1],0) + a[i] + x;
54                 dp[i][j] = max(one, two);
55                 ans[j] = max(dp[i][j], ans[j]);
56             }
57         }
58         for (int i = 0; i <= n; ++i)cout << ans[i] << " ";
59         cout << endl;
60     }
61     return 0;
62 }

 

二、前缀和做法

  先用前缀和,利用二重循环算出这个数组每一种长度的连续区间和的最大值len[i]  再用两重循环,最外层循环循环的是算加上0-n个x后的最大值,所以是i从0-n,内层循环遍历所有长度 len[j] +min(i,j)*x;就是所有长度的区间和加上x,加上x的个数必须比取区间长度和外层循环限定的加上x的个数的最小值。

  思路的关键代码:

  

 for(int i=0;i<=n;i++)//加上0~n个x后的最大值
        {
            int maxx=-0x3f3f3f3f;
            for(int k=1;k<=n;k++)
            {
                maxx=max(maxx,len[k]+min(k,i)*x);
            }
            printf("%d ",max(maxx,0));
        }

 

完整AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5050;
int a[N];
int len[N];//i长度的数组最最大值
int prefix[N];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        memset(len,-0x3f,sizeof(len));//要注意,凡是涉及到有负数的东西一定要初始设到最小
        memset(prefix,0,sizeof(prefix));
        int n,x;
        cin>>n>>x;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++) prefix[i]=prefix[i-1]+a[i];//求一个前缀和
        //for(int i=1;i<=n;i++) printf("%d-->\n",prefix[i]);
        for(int i=1;i<=n;i++)//从i位置开始k长度区间的最大值
        {
            for(int k=1;k<=n;k++)//
            {
                if(i+k-1<=n) len[k]=max(len[k],prefix[i+k-1]-prefix[i-1]);
            }
        }
        //printf("\n");
        //for(int i=1;i<=n;i++) printf("%d-->\n",len[i]);
        //printf("\n\n");
        for(int i=0;i<=n;i++)//加上0~n个x后的最大值
        {
            int maxx=-0x3f3f3f3f;
            for(int k=1;k<=n;k++)
            {
                maxx=max(maxx,len[k]+min(k,i)*x);
            }
            printf("%d ",max(maxx,0));
        }
        printf("\n");
    }
    return 0;
}

 

posted @ 2022-04-14 10:48  朱朱成  阅读(64)  评论(0编辑  收藏  举报