I 九峰与分割序列 题解(线段树维护dp)

九峰与分割序列

https://ac.nowcoder.com/acm/contest/9984/I

题目链接

题目思路

首先要想到一个dp模型

为右端点为i前一个子区间小于等于k的最大贡献 为右端点为i前一个子区间大于k的最大贡献

那么这样利用前缀和可以写出一个的dp

暴力dp

#include<bits/stdc++.h>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-3;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,k;
int a[maxn];
ll dp[maxn][2],pre[maxn];
signed main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<=n;i++){
        dp[i][0]=dp[i][1]=-INF;
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        pre[i]=pre[i-1]+a[i];
    }
    dp[0][0]=0;//
    for(int i=1;i<=n;i++){
        for(int j=0;j<=i-k-1;j++){ //[i-k,i]
            dp[i][1]=max({dp[i][1],dp[j][0]+pre[i]-pre[j],dp[j][1]+pre[i]-pre[j]});
        }
        for(int j=max(i-k,0);j<=i-1;j++){ //[j+1,i] i-j
            dp[i][0]=max({dp[i][0],dp[j][0]+pre[i]-pre[j],dp[j][1]+2*(pre[i]-pre[j])});
        }
    }
    printf("%lld\n",max(dp[n][1],dp[n][0]));
    return 0;
}

仔细观察这个dp

你会发现每次dp只需要求

区间中的

以及

区间中的

这样只要维护三个线段树即可

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-3;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,k;
int a[maxn];
ll dp[maxn][2],pre[maxn];
// dp[i][0]右端点为i前一个子区间小于等于k
// dp[i][1]右端点为i前一个子区间大于k
ll tree[maxn<<2][4];
void build(int node,int l,int r){
    if(l==r){
        tree[node][1]=tree[node][2]=tree[node][3]=-INF;
        if(l==0){
            tree[node][1]=0;
        }
        return ;
    }
    int mid=(l+r)/2;
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    tree[node][1]=max(tree[node<<1][1],tree[node<<1|1][1]);
    tree[node][2]=max(tree[node<<1][2],tree[node<<1|1][2]);
    tree[node][3]=max(tree[node<<1][3],tree[node<<1|1][3]);
}
ll query(int node,int l,int r,int L,int R,int id){
    if(L<=l&&r<=R){
        return tree[node][id];
    }
    int mid=(l+r)/2;
    ll ma=-INF;
    if(mid>=L) ma=max(ma,query(node<<1,l,mid,L,R,id));
    if(mid<R)  ma=max(ma,query(node<<1|1,mid+1,r,L,R,id));
    return ma;
}
void update(int node,int pos,int l,int r,ll val,int id){
    if(l==r){
        tree[node][id]=val;
        return ;
    }
    int mid=(l+r)/2;
    if(mid>=pos) update(node<<1,pos,l,mid,val,id);
    else   update(node<<1|1,pos,mid+1,r,val,id);
    tree[node][id]=max(tree[node<<1][id],tree[node<<1|1][id]);
}

signed main(){
    scanf("%d%d",&n,&k);
    for(int i=0;i<=n;i++){
        dp[i][0]=dp[i][1]=-INF;
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        pre[i]=pre[i-1]+a[i];
    }
    build(1,0,n);
    dp[0][0]=0;
    // 第一颗线段树维护 dp[j][0]-pre[j]
    // 第二棵线段树维护 dp[j][1]-pre[j]
    // 第二棵线段树维护 dp[j][1]-2*pre[j]
    for(int i=1;i<=n;i++){
        if(i-k-1>=0){
            dp[i][1]=max(pre[i]+query(1,0,n,0,i-k-1,1), pre[i]+query(1,0,n,0,i-k-1,2));
        }
        dp[i][0]=max(pre[i]+query(1,0,n,max(i-k,0),i-1,1),2*pre[i]+query(1,0,n,max(i-k,0),i-1,3));
        update(1,i,0,n,dp[i][0]-pre[i],1);
        update(1,i,0,n,dp[i][1]-pre[i],2);
        update(1,i,0,n,dp[i][1]-2*pre[i],3);
    }
    printf("%lld\n",max(dp[n][1],dp[n][0]));
    return 0;
}
全部评论
好家伙,这个区间从0开始建树,并且第一棵树的最后叶子是0.wa了一小时多找到n=1的例子发现有问题..真细节阿
点赞 回复 分享
发布于 2021-02-25 23:44

相关推荐

球球别再泡了:坏,我单9要了14
点赞 评论 收藏
分享
qz鹿:*** 祝他毕业就失业
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务