CF1197D Yet Another Subarray Problem [思维+线段树/dp]

传送门
题意:给一个长度为n的整数序列,定义一段连续子序列的价值为这段连续子序列之和sum(L,R) — k * f(len/m),其中k为给定的整数,len为这段连续子序列的长度,f(x)表示x向上取整,其中m为给定整数,求出连续子序列的最大价值
题解:这题并不难 (虽然我手残地写崩线段树wa了一个多小时还好最后几分钟过了)首先把sum(L,R)转化成sum[R] — sum[L]的前缀和的形式,然后想到对于每个f(len/m),位置 i 作为左端点L时,R的范围在[L+f(len/m)*m,L+(f(len/m)+1)m-1],显然对于每个i作为左端点L时,应该使sum[R]尽可能大,这显然可以用线段树求最大的sum[R],而如果对于每个i作为左端点时,如果枚举f(len/m),那么复杂度就是n * n * logn 显然不可以,而考虑到距离它m的位置i+m作为左端点时的右端点R对i的贡献 sum[R] — sum[i] — k * f(len/m)相当于求max(sum[R] — sum[i] — k * f(len/m),sum[R] — sum[i+m] — k * (f(len/m) — 1),即sum[R] — k * f(len/m) — min(sum[i],sum[i+m] — k),所以可以直接用sum[i]+k更新sum[i+m],就可以把复杂度降为nlogn

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define debug(x) cout<<#x<<" is "<<x<<endl;

const int maxn=3e5+5;
const ll inf=2e18;

ll n,m,k,a[maxn],sum[maxn];

struct Node{
    int l;
    int r;
    ll maxx;
}nod[maxn<<2];

void pushup(int rt){
    nod[rt].maxx=max(nod[rt<<1].maxx,nod[(rt<<1)|1].maxx);
}

void build(int rt,int l,int r){
    nod[rt].l=l;
    nod[rt].r=r;
    if(l==r){
        nod[rt].maxx=sum[l];
        return;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build((rt<<1)|1,mid+1,r);
    pushup(rt);
}

ll query(int rt,int l,int r,int l0,int r0){
    if(l>=l0&&r<=r0)return nod[rt].maxx;
    int mid=(l+r)>>1;
    ll xx=-inf;
    if(mid>=l0)xx=max(query(rt<<1,l,mid,l0,r0),xx);
    if(mid<r0)xx=max(query((rt<<1)|1,mid+1,r,l0,r0),xx);
    return xx;
}

int main(){
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=n;i++){scanf("%lld",&a[i]);sum[i]=sum[i-1]+a[i];}
    build(1,1,n);
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(i>=m+1)sum[i-1]=min(sum[i-1],sum[i-m-1]+k);
        ll x=query(1,1,n,i,min(i+m-1,n));
        ans=max(ans,x-sum[i-1]-k);

    }
    printf("%lld\n",ans);
    return 0;
}

由于m很小,也可以用n*m的dp做

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define debug(x) cout<<#x<<" is "<<x<<endl;

const int maxn=3e5+5;
const ll inf=2e18;

ll n,m,k,a[maxn],dp[maxn][11];

int main(){
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    ll ans=0;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            dp[i][j]=-inf;
        }
        dp[i][0]=0;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<m;j++){
            if((j-1+m)%m==0){
                if(dp[i-1][(j-1+m)%m]!=-inf)dp[i][j]=max(dp[i][j],dp[i-1][(j-1+m)%m]+a[i]-k);
            }
            else{
                if(dp[i-1][(j-1+m)%m]!=-inf)dp[i][j]=max(dp[i][j],dp[i-1][(j-1+m)%m]+a[i]);
            }
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值