Codeforces Educational Round 123 Problem C Increase Subarray Sums (DP)

本文介绍了如何使用动态规划解决Codeforces Educational Round 123中的问题C,通过分析f[i][j]状态转移并优化计算,避免了不必要的子问题。讲解了两种方法:标准DP和前缀和+枚举,并强调了状态转移的重要性。
摘要由CSDN通过智能技术生成

Codeforces Educational Round 123 Problem C Increase Subarray Sums (DP)

原题链接:C.Increase Subarray Sums

思路:
注意:此代码缺乏证明:ans[j] == max{ f[0][j] , f[1][j], …, f[n][j] }

题目让我们在数字串中,每次在不重复的数上加0 ~ n个x,并且求每次最大的子串和。

求最大子串和我们可以使用dp来求。

如:最大子序列和

这题需要在求最大子串和的时候同时计算是否在当前的数上加上x。

f[i][j]:表示以第i个数为结尾的子串的最大值,并且前i个数最多有j个数加了x

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
long long a[N], f[N][N], ans[N];
void sol()
{
	int n, x;
	cin >> n >> x;
	for(int i = 1; i <= n; i ++) cin >> a[i];
	
	for(int i = 0; i <= n; i ++)
		for(int j = 0; j <= n; j ++) f[i][j] = 0;
	
	for(int i = 0; i <= n; i ++) ans[i] = 0;
	
    for(int i = 1; i <= n; i ++) //先预处理不加x的情况
	{
		f[i][0] = max(0ll, f[i - 1][0]) + a[i];
		ans[0] = max(ans[0], f[i][0]);
	}
    
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 1; j <= i; j ++)//因为前i个数最多加上j个x 
		{
            //当前的数没有加上x
			f[i][j] = max(0ll, f[i - 1][j - 1]) + a[i];  
            //如果当前的数加上了x
            f[i][j] = max(f[i][j], max(0ll,f[i - 1][j - 1]) + a[i] + x);
            /*
            容易看出上面两种情况的大小只取决于x的正负
            如果x大于0则加上x大,小于0则不加x大
          	因为x>=0	
			f[i][j] = max(0ll, f[i - 1][j - 1]) + a[i] + x;
            */
            //更新答案        
			ans[j] = max(ans[j], f[i][j]);
		}
        /*
		f[i][j]:表示以第i个数为结尾的子串的最大值,并且前i个数最多有j个数加了x
		显然,对于 0 <= k <= i, f[i][k] <= f[i][j] 一定成立
		所以,f[i][i]代表以第i个数为结尾的子串的最大值
		
		由题意,前i个数最多只能加上i个x,所以当j>i时, 其最大值就是f[i][i]
		
		形象的理解就是: 在这个字串之外也有数加了x,但是和我们这个子串没有关系
		我们不用管第i个数后面的数是多少,它是否加了x,因为我们根本不去取它
		*/
        
        //更新j>i时的答案
		for(int j = i + 1; j <= n; j ++)
			ans[j] = max(ans[j], f[i][i]);
        /*
        显然,这样更新出来的答案肯定是不会漏掉任何一种的情况的。那么我们是否可以不计算j>i的情况呢?
        答案是不可以。
        我们在状态转移的时候更新的答案
        仅仅是前i个数里最多有j个数加上了x,并且是以第i个数结尾的子串的最大值
        并没有考虑到不以第i个数结尾的子串的最大值,
        所以会漏掉一部分答案
        */

	}
		
	for(int i = 0; i <= n; i ++) cout << ans[i] << ' ';
	cout << '\n';
}
int main()
{
	int t;
	cin >> t;
	while(t --) sol();
	return 0;
}

下面给出前缀和+枚举的做法:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5010;
long long a[N], b[N], m[N], ans[N];
void sol()
{
	int n, x;
	cin >> n >> x;
	
	b[0] = 0;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
		b[i] = b[i - 1] + a[i];
	}
	
	for(int i = 0; i <= n; i ++) m[i] = -2e9;
	for(int i = 1; i <= n; i ++)
		for(int j = i; j <= n; j ++)
			m[j - i + 1] = max(m[j - i + 1], b[j] - b[i - 1]);
			//长度为1~n的子串最大和 
	
	long long curmax = 0;
	for(int i = 1; i <= n; i ++) curmax = max(curmax, m[i]); //不加x的最大子串和 
	ans[0] = curmax;
	for(int k = 1; k <= n; k ++) //加k个x
	{
		for(int i = k; i <= n; i ++) //长度大于等于k的都可以加k个x
			curmax = max(curmax, m[i] + k * x); 
		ans[k] = curmax;	
	}	

	for(int i = 0; i <= n; i ++) cout << ans[i] << ' ';
	cout << '\n';
}
int main()
{
	int t;
	cin >> t;
	while(t --) sol();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值