• [题解]Yet Another Subarray Problem-DP 、思维(codeforces 1197D)


    题目链接: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.htmlhttp://blog.leanote.com/post/icontofig/Educational-Codeforces-Round-69

    正因为是最弱,所以才理解智慧之强
  • 相关阅读:
    node-webkit 不支持html5_video播放mp4的解决方法
    node-webkit(Windows系统) 打包成exe文件后,被360杀毒软件误报木马的解决方法
    剑指 Offer 36. 二叉搜索树与双向链表
    剑指 Offer 33. 二叉搜索树的后序遍历序列
    Leetcode96. 不同的二叉搜索树
    Leetcode95. 不同的二叉搜索树 II
    leetcode1493. 删掉一个元素以后全为 1 的最长子数组
    Leetcode1052. 爱生气的书店老板
    Leetcode92. 反转链表 II
    Leetcode495. 提莫攻击
  • 原文地址:https://www.cnblogs.com/Yanick/p/11290641.html
Copyright © 2020-2023  润新知