[SHOI2015]脑洞治疗仪

[SHOI2015]脑洞治疗仪

[题目链接]

链接

[思路要点]

较为容易

线段树维护区间的左边一段连续 \(0\) 的数量,右边一段 \(0\) 的数量,除了两边两段 \(0\) 以外的连续的 \(0\) 的数量最大值,该区间 \(0\) 的总数以及该区间是否全部为 \(0\)

然后更新很显然,每个都讨论一下

对于 \(0\) 操作,就是一次区间赋值

对于 \(1\) 操作,首先找到 \([l_0,r_0]\) 区间内的 \(1\) 的个数,然后在 \([l_1,r_1]\) 区间内线段树二分,找到某个前缀的 \(0\) 的数量正好是之前 \(1\) 的个数的位置,然后区间赋值成 \(1\) 即可

对于 \(2\) 操作,显然的区间询问,注意两边的处理即可

[代码]

#include<cstdio>
#include<iostream>
#ifdef ONLINE_JUDGE
char ss[1<<17],*A=ss,*B=ss;
inline char gc(){if(A==B){B=(A=ss)+fread(ss,1,1<<17,stdin);if(A==B)return EOF;}return*A++;}
template<class T>inline void read(T&x){
    static char c;static int y;
    for(c=gc(),x=0,y=1;c<48||57<c;c=gc())if(c=='-')y=-1;
    for(;48<=c&&c<=57;c=gc())x=((x+(x<<2))<<1)+(c^'0');
    x*=y;
}
#else
void read(int &x){scanf("%d",&x);}
#endif      //快读
using namespace std;
int ad[800001],sum[800001],n,t;
inline void write(int x){if(x>9) write(x/10);putchar(x%10^48);}
inline void pushup(int rt){
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void build(int rt,int l,int r){     //菜鸡专属建树
    if (l==r){
        sum[rt]=1;
        return;
    }
    int m=(l+r)>>1;
    build(rt<<1,l,m);
    build(rt<<1|1,m+1,r);
    pushup(rt);
}
inline void pushdown(int rt,int l,int r){
    if (ad[rt]){
        int m=(l+r)>>1;
        ad[rt]--;
        sum[rt<<1]=ad[rt]*(m-l+1);
        sum[rt<<1|1]=ad[rt]*(r-m);
        ad[rt<<1]=ad[rt]+1;
        ad[rt<<1|1]=ad[rt]+1;
        ad[rt]=0;
    }
}
void update(int rt,int l,int r,int x,int y,int k){  //区间都附成k
    if (l>y||x>r) return;
    if (x<=l&&r<=y){
        ad[rt]=k+1;     //由于有0,k+1方便一些
        sum[rt]=k*(r-l+1);
        return;
    }
    pushdown(rt,l,r);
    int m=(l+r)>>1;
    if (m>=x) update(rt<<1,l,m,x,y,k);
    if (m<y) update(rt<<1|1,m+1,r,x,y,k);
    pushup(rt);
}
int query(int rt,int l,int r,int x,int y){      //查询区间和
    if (l>y||x>r) return 0;
    if (x<=l&&r<=y) return sum[rt];
    pushdown(rt,l,r);
    int m=(l+r)>>1,ret=0;
    if (m>=x) ret+=query(rt<<1,l,m,x,y);
    if (m<y) ret+=query(rt<<1|1,m+1,r,x,y);
    return ret;
}
inline int ef(int x,int y,int k){   //二分查找x到y之间连续最长的k序列
    int l=0,r=y-x,mid;
    if (x>y) return -1;
    while (l<=r){
        mid=(l+r)>>1;
        if (query(1,1,n,x,x+mid)==k*(mid+1)) l=mid+1;
        else r=mid-1;
    }
    return l;
}
int main(){
    int x,y,b,c,d;
    read(n); read(t);
    build(1,1,n);
    while (t--){
        read(b),read(x),read(y);
        if (b==0){
            update(1,1,n,x,y,0);
        }
        if (b==1){
            read(c),read(d);
            int num=query(1,1,n,x,y),p=0;
            update(1,1,n,x,y,0);
            if (num>=(d-c+1)-query(1,1,n,c,d)){ 
                update(1,1,n,c,d,1); continue;
            }
            if (num==0) continue;//以上两个特判为菜鸡T了之后无助的挣扎
            while (1){
                if (query(1,1,n,c,c)==1){   //开头为一
                    p=ef(c,d,1); c+=p; 
                }
                p=ef(c,d,0);
                if (p==-1) break;
                if (p>num){
                    update(1,1,n,c,c+num-1,1);
                    break;
                }
                else{
                    update(1,1,n,c,c+p-1,1);
                    num-=p; c+=p;
                }
            }
        }
        if (b==2){
            int ans=0,p=0;
            while (1){
                if (query(1,1,n,x,x)==1){
                    p=ef(x,y,1); x+=p;
                }
                p=ef(x,y,0);
                if (p==-1) break;
                ans=max(ans,p);
                x+=p; if (x>y) break;   //忘写这个85分死循环
            }
            write(ans); puts("");
        }
    }
    return 0;
}
posted @ 2019-07-01 13:42  wawawa8  阅读(155)  评论(0编辑  收藏  举报