codeforces_1108_E2

http://codeforces.com/contest/1108/problem/E2

题意:

给你一个长度为n的序列,m个区间,你可以选择若干的区间,对区间内所有数-1。然后设x为修改后的数组中最大元素-最小元素的大小,求最大的x。以及让你给出一个实现的方案,即需要选择几个区间,选择哪些区间。任意输出一种方案即可。

题解:

简要:

枚举每个点,把包含这个点的所有边加上,把之前加了但是现在不包含这个点的边删除,直接上线段树维护就可以了。

详细:

在E1中,我们可以通过这个题中优美的性质,对于每一个区间ai,通过枚举在这个区间的以及不在这个区间的两个点,我们就可以用O(n^2m)的时间复杂度进行求解。

但是在这个题中,n的范围在10^5,因此我们需要用一些数据结构进行优化。

我们考虑这几种情况,对于每一个区间操作,如果区间内的最小值恰好在要更新的区间内,而最大值不在,则此时答案必定更优;如果最小值和最大值恰在要更新的区间内,则答案不变;如果最大值在而最小值不在,则答案必定更差。

至此,我们可以发现,要使得答案不会变差,当且仅当最小值恰好在要更新的区间内。

但是我们目前并不知道哪一个点作为最小值点更优,因此我们可以枚举最小值点的位置pos。根据我们之前的分析,最小值点能够防止答案变差,因此那些能够把我们所枚举的pos包含的区间必定要更新。而一个区间要包含一个点,则这个区间至少是那些以该点为起始点的区间。因此我们只需要在枚举最小值点位置的过程中,不断的进行区间−1即可。

而我们需要注意的是,在我们枚举的过程中,倘若我们之前更新的某一个区间不能包含当前的位置,我们需要把之前的影响消去,否则会导致将区间的最大值也减1,导致答案不正确。因此,我们只需要在之前枚举的过程的最后,把以当前位置pos为结尾的所有区间的影响消去即可。

因为存在区间更新以及区间求最大值,因此我们可以用线段树进行维护。

总的时间复杂度为O(nlogn)

code:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int a[100004];
struct line{
    int l,r,id;
    bool friend operator < (line a ,line b){
        if(a.l==b.l)return a.r<b.r;
        else return a.l<b.l;
    }
}l[304],r[304];
 
struct node{
    int l,r,w,laz;
}t[400004];
 
void build(int rt,int l,int r){
    t[rt]={l,r,0,0};
    if(l==r)t[rt].w=a[l];
    else {
        int mid=(l+r)>>1;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
        t[rt].w=max(t[rt<<1].w,t[rt<<1|1].w);
    }
}
 
void pushdown(int rt){
    if(t[rt].laz==0)return ;
    t[rt<<1].laz+=t[rt].laz;
    t[rt<<1|1].laz+=t[rt].laz;
    t[rt<<1].w+=t[rt].laz;
    t[rt<<1|1].w+=t[rt].laz;
    t[rt].laz=0;
}
 
void update(int rt,int l,int r,int w){
    if(t[rt].l>=l&&t[rt].r<=r){
        t[rt].laz+=w;
        t[rt].w+=w;
    } else{
        pushdown(rt);
        int mid=(t[rt].l+t[rt].r)>>1;
        if(l<=mid)update(rt<<1,l,r,w);
        if(r>mid)update(rt<<1|1,l,r,w);
        t[rt].w=max(t[rt<<1].w,t[rt<<1|1].w);
    }
}
int main(){
    int n,m;
    cin>>n>>m;
    for (int i = 1; i <= n; ++i) {
        scanf("%d",&a[i]);
    }
    for (int i = 1; i <=m ; ++i) {
        scanf("%d%d",&l[i].l,&l[i].r);
        l[i].id=i;
        r[i]=l[i];
    }
    sort(l+1,l+1+m);
    build(1,1,n);
    int cnt1=1,cnt2=1;
    int ans=-1000000000;
    vector<int>v;
    vector<int>ansv;
    for (int i = 1; i <= n; ++i) {
        //这里加区间删区间可以用优先队列优化
        while(cnt2<=m&&l[cnt2].l<=i){
            update(1,l[cnt2].l,l[cnt2].r,-1);
            v.push_back(l[cnt2].id);
            cnt2++;
        }
        for (int j = 0; j < v.size(); ++j) {
            if(r[v[j]].r<i){
                update(1,r[v[j]].l,r[v[j]].r,1);
                v.erase(v.begin()+j);
                j--;
            }
        }
        if(ans<t[1].w-a[i]+(int)v.size()){
            ans=t[1].w-a[i]+(int)v.size();
            ansv=v;
        }
    }
    if(ans==-1000000000){
        puts("0");
        puts("0");
        return 0;
    }
    printf("%d\n",ans);printf("%d\n",ansv.size());
    if(ansv.size()>0){
 
        for (int i = 0; i < ansv.size(); ++i){
            printf("%d ",ansv[i]);
        }
    }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值