题意:
在一个数组里面,找一个连续子数组使得子数组和最大,这是最大子段和。
现在给定一个数x,要求,求数组中的某一个连续子数组乘上x之后,该数组的最大字段和是多少。
解法:
第一眼:毫无疑问这是线性DP,而且肯定就是最大字段和拓展一下。
第二眼:如果x是正的话,那就找出最大子段和然后乘上x,如果x是负的话,那就找出最小子段和,并且记录这一个最小子段和的位置,然后给对应位置乘上x后,在对原数组进行最大子段和。连wa。。。恍然醒悟,这种贪心的做法似乎不适用,如果x大于0的话显然是没问题的,不过如果x小于0,将最小子段和乘x并不能保证可以得到最优解。
例如:3 -1
-11 10 -11 ,明显只需要变一个11就行了。
第三眼:既然贪心不可行,尝试动态规划,声明dp[maxx][2],dp[i][0]:表示到i为止没有乘m的最大值,dp[i][1]:表示到i为止已经乘了m的最大值,那么转移就可以是:
dp[i][0] = max(dp[i-1][0]+a[i],a[i]); dp[i][1] = max(max(dp[i-1][0]*m+a[i],dp[i-1][0]+a[i]*m),max(dp[i-1][1]+a[i],a[i]*m));
然而这种状态好像有问题,因为他只考虑了一个前面一部分元素乘m也就是dp[i-1][0]*m或者是当前元素乘m,但是并没有考虑从当前开始后x位乘m。也就是说,他一直考虑的是从开始到i位置的最大子段和乘m,并没有考虑到中间某一部分乘m的情况。
最后:如何维护中间某一段元素乘m的情况呢,那就给dp数组加一个表示中间相乘就ok。
定义:dp[max][3]:dp[i][0]:表示到i为止没有乘m的最大值,dp[i][1]:表示不知道从什么时候开始,但是一直乘m乘到第i个位置(处于正在被乘的区间),dp[i][2]:前面已经有段区间乘完m了,所以现在不能乘m了。这样子的状态就考虑到了中间某一部分。
转移:
dp[i][0] = max(dp[i-1][0]+a[i],a[i]);
dp[i][1] = max(dp[i-1][0]+a[i]*m,max(dp[i-1][1]+a[i]*m,a[i]*m));
dp[i][2] = max(dp[i-1][1]+a[i],max(dp[i-1][2]+a[i],a[i]));
代码如下:
/*by kzl*/
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
using namespace std;
const int maxx = 1e6+500;
const int INF = 0x3f3f3f3f;
typedef long long ll;
int n,m;
ll a[maxx];
char s[maxx];
ll mi = 1e18,ma = -1*1e18;
ll dp[maxx][3];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++){
dp[i][0] = max(dp[i-1][0]+a[i],a[i]);
dp[i][1] = max(dp[i-1][0]+a[i]*m,max(dp[i-1][1]+a[i]*m,a[i]*m));
dp[i][2] = max(dp[i-1][1]+a[i],max(dp[i-1][2]+a[i],a[i]));
ma = max(ma,max(dp[i][0],dp[i][1]));
ma = max(ma,dp[i][2]);
}
printf("%lld\n",max(ma,0ll));
return 0;
}
以上