题目链接:https://codeforces.com/problemset/problem/1197/D
题意:
给你一个序列,求一个子序列 a[l]~a[r] 使得该子序列的 sum(l,r)-k*(r-l+1)/m(向上取整)的值是在所有子序列中最大的,并输出最大值
思路:
法一:动态规划
dp[i][j] 表示序列到i截止,这一轮已经进行了j次取数(j = (len+m-1)%m)
那么dp[i][j]维护的就是起点为 s = i-j+1-m*t (t>=0)这个集合的最优,这样所有的 dp[i][j] 就可以维护以 i 截止的最优答案了
对于当前i更新有两种情况:
第一种是只取当前这个 a[i] 这个在 dp[i][1] 特判即可
第二种是还要取前面的,dp[i][j] 从 dp[i-1][j-1](因为 dp[i-1][j-1] 所维护的s集合和 dp[i][j] 所维护的s集合是一样的)转移即可(注意边界条件)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 typedef long long ll; 7 const ll inf=200000000000000; 8 9 int n,m; 10 ll ans=0,dp[300005][20],sum[300005],a[300005],k; 11 12 int main() 13 { 14 scanf("%d%d%lld",&n,&m,&k); 15 for(int i=1;i<=n;i++) 16 { 17 scanf("%lld",&a[i]); 18 sum[i]=sum[i-1]+a[i]; 19 } 20 for(int i=1;i<=n;i++) 21 { 22 for(int j=0;j<=m;j++) dp[i][j]=-inf; 23 } 24 dp[1][1]=a[1]-k; 25 for(int i=2;i<=n;i++) 26 { 27 dp[i][1]=a[i]-k; 28 for(int j=1;j<=min(i,m);j++) 29 { 30 if(j==1) dp[i][j]=max(dp[i][j],dp[i-1][m]+a[i]-k); 31 else dp[i][j]=max(dp[i][j],dp[i-1][j-1]+a[i]); 32 } 33 } 34 for(int i=1;i<=n;i++) 35 { 36 for(int j=1;j<=m;j++) ans=max(ans,dp[i][j]); 37 } 38 cout<<ans<<endl; 39 return 0; 40 }
法二:尺取法
多加了一层维护 start_point%m=rnd,进行m次尺取法即可
(在时间够的情况下,搞不清楚当前单调队列弹出几个是最优的,那么就枚举,这样就不用担心前面要弹出什么了,只需在 len%m=0 时判断是否要把起始点重置即可)
即如果当前的和减去 k*t 小于0,那么就重新开始,否则继续加
注意 ans 在每一次向后扩展时都要更新
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=300005; 5 ll ans=0,n,a[N],m,k; 6 7 int main() 8 { 9 scanf("%lld%lld%lld",&n,&m,&k); 10 for(int i=1;i<=n;i++) scanf("%lld",&a[i]); 11 for(int rnd=1;rnd<=m;rnd++){ 12 ll len=0; ll now=0; 13 for(int i=rnd;i<=n;i++){ 14 if(len%m==0) if(now-len/m*k<0) now=0,len=0; 15 now+=a[i]; len++; 16 ans=max(ans,now-(len+m-1)/m*k); 17 } 18 } 19 cout<<ans<<endl; 20 return 0; 21 }
法三:前缀和
我们可以发现 m 很小,只有10,而当子段长度能整除以 m 的时候,再添加一个才会使得我们多去减一个 k
我们可以让所有位置对 m 取模,分成 0—m-1 这样的剩余系,我们枚举剩余系,以剩余系中的位置作为结尾求最大值
当我们枚举到剩余系i的时候,我们另所有处于剩余系i的位置上的数 -k,之后我们直接扫一遍序列,不断累加并和 0 求最大值,遇到可结束位置时,与答案取最大值并更新答案
这样子我们可以再 O(nm) 的时间复杂度下做出这道题
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn = 3e5+5; 5 int n,m,k,a[maxn],b[maxn]; 6 7 int main(){ 8 cin>>n>>m>>k; 9 for(int i=1;i<=n;i++) 10 cin>>a[i]; 11 ll ans=0,s=0; 12 for(int j=0;j<m;j++){ 13 for(int i=1;i<=n;i++) 14 if(i%m==j) 15 b[i]=a[i]-k; 16 else 17 b[i]=a[i]; 18 s=0; 19 for(int i=1;i<=n;i++){ 20 s=max(s+b[i],0ll); 21 if(i%m==j) 22 ans=max(ans,s); 23 } 24 } 25 cout<<ans<<endl; 26 return 0; 27 }
参考:https://www.cnblogs.com/Forever-666/p/11241525.html、http://blog.leanote.com/post/icontofig/Educational-Codeforces-Round-69