P5069 [Ynoi2015] 纵使日薄西山

纵使日薄西山,我也想保护你..

先不考虑单点修改

手玩一下样例

可以发现你一直操作一个数的结果是和答案一样的

可以大致证明一下

x y z

如果选择y操作,那么x,z必然不能操作,因为x,z会和y一直减少,只有y做出了贡献,所以我们的答案应该是选的数之和

因此应该只有2种操作方法

操作1 ,3,5,7...

操作2,4,6,8...

也就是维护一个奇偶性相同的单增或者单减序列

挺抽象的,这种做法待补

我们可以用线段树来维护需要被操作的值(合并很妙)

我们先思考一下,我们需要维护哪些值,左端点有没有被操作的,右端点有没有被操作的,以及如果左端点需要被操作的贡献是多少,右端点需要被操作的贡献是多少,如果两端都被操作的贡献是多少,因此我们维护四颗线段树,seg[N<<2][4]。

0是左右两端都没有被操作

1是左端点需要被操作

2是右端点需要被操作

3是左右端点都要被操作

struct node{
    ll lf,rf,val;
}seg[N<<2][4];// 1l,2r,3all,0no

感觉也很抽象我们可以举一个例子(标记X是需要被操作的,O是在其他操作范围区间的)

134554
0XXXX
1OXOX
2XOXO
3OXOOXO

假设 1 3 4 5 5 4合并

134554
0XXX
1OXXX
2XXO
3OXXO

可以发现[0]的左边是从2转移过来的,右边从[0]转移过来的,判断依据是5>4

因此就是a[mid]与a[mid+1]的大小关系

接下来,我们就仔细分析每种情况的转移方案

1. if()在两端都不用管的情况下,如果左子树的右端点有标记,右子树的左端点有标记

(i)左边大于等于右边

那选择左边直接转移,右边转移左边有端点的那种情况,答案求和是左边(不管)+右边(左边要管)

(ii)右边大于左边

那选择右边直接转移,左边转移右边有端点的那种情况,答案求和是左边(右边要管)+右边(不管)

if(seg[tl(id)][0].rf && seg[tr(id)][0].lf){//左,右端点都存在
        if(a[mid]>=a[mid+1]){//左边大
            seg[id][0].lf=seg[tl(id)][0].lf;//选左边
            seg[id][0].rf=seg[tr(id)][1].rf;//转移如果左边要管的右边的rf
            seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][1].val;//左边(都要管)+右边(管左边)
        }else{//右边大
            seg[id][0].lf=seg[tl(id)][2].lf;//转移如果右边要管的左边的lf
            seg[id][0].rf=seg[tr(id)][0].rf;//选右边
            seg[id][0].val=seg[tl(id)][2].val+seg[tr(id)][0].val;//左边(管右边)+右边(都要管)
        }

else()直接转移左右两边,答案也直接加

else{//直接拼
            seg[id][0].lf=seg[tl(id)][0].lf;
            seg[id][0].rf=seg[tr(id)][0].rf;
            seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][0].val;
    }

2.if()在要管左边的左子树有右端点标记以及不管的右子树有左端点

因为存在要管左边的,所以不用转移左边,考虑右边的转移即可

(i)左边大于等于右边

转移需要管左边的右边,答案求和是左边(管左边)+右边(管左边)

(ii)右边大于左边

直接转移,答案求和是左边(管两边)+右边(不管)

if(seg[tl(id)][1].rf && seg[tr(id)][0].lf){//左,右端点存在
        if(a[mid]>=a[mid+1]){
            seg[id][1].rf=seg[tr(id)][1].rf;
            seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][1].val;
        }else{
            seg[id][1].rf=seg[tr(id)][0].rf;
            seg[id][1].val=seg[tl(id)][3].val+seg[tr(id)][0].val;
        }

else() 直接转移

else{
        seg[id][1].rf=seg[tr(id)][0].rf;
        seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][0].val;
    }

3.if()不管的左子树有右端点以及管右边的右子树有左端点

同理考虑左端点的转移

(i)左边大于等于右边

转移需要不管的左边,答案求和是左边(不管)+右边(管两边)

(ii)右边大于左边

直接转移,答案求和是左边(管右边)+右边(管右边)

if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
        if(a[mid]>=a[mid+1]){
            seg[id][2].lf=seg[tl(id)][0].lf;
            seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
        }else{
            seg[id][2].lf=seg[tl(id)][2].lf;
            seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
        }

else()直接转移

if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
        if(a[mid]>=a[mid+1]){
            seg[id][2].lf=seg[tl(id)][0].lf;
            seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
        }else{
            seg[id][2].lf=seg[tl(id)][2].lf;
            seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
        }

4.if() 要管左边的左子树有右端点标记,要管右边的右子树有左端点标记

不用考虑左右端点转移

(i)左边大于等于右边

答案求和是左边(管左边)+右边(管两边)

(ii)右边大于左边

答案求和是左边(管两边)+右边(管右边)

if(seg[tl(id)][1].rf && seg[tr(id)][2].lf){
        if(a[mid]>=a[mid+1]){
            seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][3].val;
        }else{
            seg[id][3].val=seg[tl(id)][3].val+seg[tr(id)][2].val;
        }

else()直接转移

 seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][2].val;

讨论结束

可以发现只有中间存在重叠的情况才需要讨论左右端点的标记转移,其他情况直接转移即可

文字描述很拉垮,可以直接看码.

完整代码

#include<iostream>
#include<cmath>
#include<algorithm>
#include<bitset>
#include<cstring>
#include<vector>
#define INF (1ll<<30)
using namespace std;
typedef long long ll;
const int N=1e5+9;
namespace Lan {
    inline string sread() {
        string s=" ";char e=getchar();
        while(e==' '||e=='\n')e=getchar();
        while(e!=' '&&e!='\n')s+=e,e=getchar();
        return s;
    }
    inline void swrite(string s){
        for(char e:s)putchar(e);
        printf("\n");
    }
    inline ll read() {
        ll x=0,y=1;char c=getchar();
        while(!isdigit(c)){if(c=='-')y=-1;c=getchar();}
        while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
        return x*=y;
    }
    inline void write(ll x) {
        if(x<0){x=-x,putchar('-');}ll sta[35],top=0;
        do sta[top++]=x%10,x/=10;while(x);
        while(top)putchar(sta[--top]+'0');
    }
}using namespace Lan;
int a[N];
struct node{
    ll lf,rf,val;
}seg[N<<2][4];// 1l,2r,3all,0no
//  1 3 4 
//1 X   X
//2 X   X
//3 X   X   
//0 X   X
//  5 5 4
//1     X
//2 X
//3    
//0 X   X
ll tl(ll x){return x<<1;}
ll tr(ll x){return x<<1|1;}
void pushup(int id,int l,int r){
    int mid=(l+r)>>1;
    if(seg[tl(id)][0].rf && seg[tr(id)][0].lf){//左,右端点都存在
        if(a[mid]>=a[mid+1]){//左边大
            seg[id][0].lf=seg[tl(id)][0].lf;//选左边
            seg[id][0].rf=seg[tr(id)][1].rf;//转移如果左边要管的右边的rf
            seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][1].val;//左边(都要管)+右边(管左边)
        }else{//右边大
            seg[id][0].lf=seg[tl(id)][2].lf;//转移如果右边要管的左边的lf
            seg[id][0].rf=seg[tr(id)][0].rf;//选右边
            seg[id][0].val=seg[tl(id)][2].val+seg[tr(id)][0].val;//左边(管右边)+右边(都要管)
        }
    }else{//直接拼
            seg[id][0].lf=seg[tl(id)][0].lf;
            seg[id][0].rf=seg[tr(id)][0].rf;
            seg[id][0].val=seg[tl(id)][0].val+seg[tr(id)][0].val;
    }
    if(seg[tl(id)][1].rf && seg[tr(id)][0].lf){//左,右端点存在
        if(a[mid]>=a[mid+1]){
            seg[id][1].rf=seg[tr(id)][1].rf;
            seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][1].val;
        }else{
            seg[id][1].rf=seg[tr(id)][0].rf;
            seg[id][1].val=seg[tl(id)][3].val+seg[tr(id)][0].val;
        }
    }else{
        seg[id][1].rf=seg[tr(id)][0].rf;
        seg[id][1].val=seg[tl(id)][1].val+seg[tr(id)][0].val;
    }
    if(seg[tl(id)][0].rf && seg[tr(id)][2].lf){
        if(a[mid]>=a[mid+1]){
            seg[id][2].lf=seg[tl(id)][0].lf;
            seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][3].val;
        }else{
            seg[id][2].lf=seg[tl(id)][2].lf;
            seg[id][2].val=seg[tl(id)][2].val+seg[tr(id)][2].val;
        }
    }else{
        seg[id][2].lf=seg[tl(id)][0].lf;
        seg[id][2].val=seg[tl(id)][0].val+seg[tr(id)][2].val;
    }
    if(seg[tl(id)][1].rf && seg[tr(id)][2].lf){
        if(a[mid]>=a[mid+1]){
            seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][3].val;
        }else{
            seg[id][3].val=seg[tl(id)][3].val+seg[tr(id)][2].val;
        }
    }else{
        seg[id][3].val=seg[tl(id)][1].val+seg[tr(id)][2].val;
    }
}
void build(int id,int l,int r){
    if(l==r){
        seg[id][0].val=a[l];
        seg[id][0].lf=1;
        seg[id][0].rf=1;
    }else{
        int mid=(l+r)>>1;
        build(tl(id),l,mid);
        build(tr(id),mid+1,r);
        pushup(id,l,r);
    }
}
void update(int id,int l,int r,int pos,int val){
    if(l==r){
        seg[id][0].val=val;
    }else{
        int mid=(l+r)>>1;
        if(mid>=pos){
            update(tl(id),l,mid,pos,val);
        }else{
            update(tr(id),mid+1,r,pos,val);
        }
        pushup(id,l,r);
    }
}
int main(){
    // ios::sync_with_stdio(false);
    // cin.tie(0),cout.tie(0);
    int n;
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    build(1,1,n);
    int q;
    q=read();
    while(q--){
        int x,y;
        x=read(),y=read();
        a[x]=y;
        update(1,1,n,x,y);
        cout<<seg[1][0].val<<'\n';
    }
    return 0;
}

再次总结,ynoi题的真好,又学了一下如何维护多个量,lxl orz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值