[2017纪中10-24]筹备计划 线段树

题面
设a[i]为i点学生个数。
考虑用线段树维护这么几个东西:sum0(区间内a[i]和),sum1(区间内a[i]*i和),d0(区间是否全部不可用),d1(区间是否全部可用)。
sum0,sum1只有单点修改,d0,d1只有区间覆盖。
那么我们可以O(logn)计算下列东西:
不考虑可用不可用的最优答案:就是中位数,通过sum0就可以搞定。
某一点X的答案:sum0[1,x-1] * x-sum1[1,x-1]+sum1[x+1,n]-sum0[x+1,n] * x。
某一点往左/往右第一个可用点:通过d0,d1在线段树上查找。
我们发现,如果最优答案不可用,那么答案一定在它左边第一个可用点和右边第一个可用点的较小值,可以用上述维护的东西计算出。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=200010;
int n,q;
ll tot=0,a[maxn];
int read()
{
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
    return x;
}
struct tree
{
    int l,r,b;
    ll sum[2];
    bool d0,d1;
    tree *ls,*rs;
    tree()
    {
        ls=rs=NULL;
        l=r=sum[0]=sum[1]=0;
        d0=0;d1=1;b=-1;
    }
    void cal()
    {
        if(b==0) {d0=1;d1=0;}
        if(b==1) {d0=0;d1=1;}
    }
    void pushdown()
    {
        if(b>=0)
        {
            ls->b=b;rs->b=b;
            ls->cal();rs->cal();
            b=-1;
        }
    }
    void update()
    {
        for(int i=0;i<=1;i++)
        sum[i]=ls->sum[i]+rs->sum[i];
        d0=ls->d0&&rs->d0;
        d1=ls->d1&&rs->d1;
    }
    void build(int lx,int rx)
    {
        l=lx;r=rx;
        if(l==r) {sum[0]=a[l];sum[1]=sum[0]*l;return ;}
        int mid=(l+r)>>1;
        (ls=new tree)->build(lx,mid);
        (rs=new tree)->build(mid+1,rx);
        update();
    }
    void add(int pl,int c)
    {
        if(l==r) {sum[0]+=c;sum[1]+=(ll)c*pl;return ;}
        pushdown();
        int mid=(l+r)>>1;
        if(pl<=mid) ls->add(pl,c);
        else rs->add(pl,c);
        update();
    }
    void mdf(int lx,int rx,int f)
    {
        if(lx==l&&rx==r) {b=f;cal();return ;}
        pushdown();
        int mid=(l+r)>>1;
        if(rx<=mid) ls->mdf(lx,rx,f);
        else if(lx>mid) rs->mdf(lx,rx,f);
        else {ls->mdf(lx,mid,f);rs->mdf(mid+1,rx,f);} 
        update();
    }
    int qz(ll k)
    {
        if(l==r) {return l;}
        pushdown();
        ll t=ls->sum[0];
        if(k<=t) return ls->qz(k);
        else return rs->qz(k-t);
    }
    ll qsum(int lx,int rx,int f)
    {
        if(lx>rx) return 0;
        if(lx==l&&rx==r) return sum[f];
        pushdown();
        int mid=(l+r)>>1;
        if(rx<=mid) return ls->qsum(lx,rx,f);
        else if(lx>mid) return rs->qsum(lx,rx,f);
        else return ls->qsum(lx,mid,f)+rs->qsum(mid+1,rx,f);
    }
    int findl(int p)
    {
        if(d0) return -1;
        if(l==r) return l;
        pushdown();
        int mid=(l+r)>>1,tmp=-1;
        if(p>mid) tmp=rs->findl(p);
        if(tmp==-1) tmp=ls->findl(min(p,mid));
        return tmp;
    }
    int findr(int p)
    {
        if(d0) return -1;
        if(l==r) return l;
        pushdown();
        int mid=(l+r)>>1,tmp=-1;
        if(p<=mid) tmp=ls->findr(p);
        if(tmp==-1) tmp=rs->findr(max(p,mid+1));
        return tmp;
    }
}*xtr;
ll calc(int x)
{
    if(x==-1) return 1e18;
    return xtr->qsum(1,x-1,0)*x-xtr->qsum(1,x-1,1)+xtr->qsum(x+1,n,1)-xtr->qsum(x+1,n,0)*x;
}
int main()
{
    n=read();q=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        tot+=a[i];
    }
    (xtr=new tree)->build(1,n);
    bool pd=1;
    for(int i=1;i<=q;i++)
    {
        int opt=read(),x=read(),y=read();
        if(opt==1){xtr->add(x,y);tot+=y;a[x]+=y;}
        if(opt==2){xtr->add(x,-y);tot-=y;a[x]-=y;}
        if(opt==3){xtr->mdf(x,y,1);}
        if(opt==4){xtr->mdf(x,y,0);}
        if(opt==3||opt==4) pd=0;
        int md=xtr->qz((tot+1)/2),lb,rb;
        if(pd) {printf("%d\n",md);continue;}
        lb=xtr->findl(md);rb=xtr->findr(md);
        if(lb==md||rb==md) {printf("%d\n",md);continue;}
        if(lb==-1&&rb==-1) {puts("-1");continue;}
        if(calc(lb)<=calc(rb)) printf("%d\n",lb);
        else printf("%d\n",rb);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值