P9388 [THUPC 2023 决赛] 先人类的人类选别

· · 题解

Description

https://www.luogu.com.cn/problem/P9388

Solution

观察到操作序列一定,操作顺序对答案并没有影响。

将答案拆为 \sum\limits_{i\le r}a_i-\sum\limits_{i<l}a_i ,只需要求出操作后的前缀和即可。

观察到对于一个前缀区间,操作的本质就是将当前操作的所有 xa_i 扔到一个堆里,取最小的前 q 扔给后面。所以只需要快速找到前 q 小之和即可。对于序列和操作分别用主席树和权值线段树,查询两个一起跑。

没有卡常。

#define int long long
#define maxn 500005
int n,m;
int a[maxn],suf[maxn],root[maxn],rota,cnt;
struct node {
    int ls,rs,sum,siz;
} f[maxn*30];
int Update(int l,int r,int pre,int head) {
    int rt=++cnt;
    f[rt]=f[pre];
    f[rt].siz++;
    f[rt].sum+=head;
    if (l==r) return rt;
    int mid=l+r>>1;
    if (head<=mid) f[rt].ls=Update(l,mid,f[rt].ls,head);
    else f[rt].rs=Update(mid+1,r,f[rt].rs,head);
    return rt;
}
void Update2(int l,int r,int &rt,int head) {
    if (!rt) rt=++cnt;
    f[rt].siz++,f[rt].sum+=head;
    if (l==r) return ;
    int mid=l+r>>1;
    if (head<=mid) Update2(l,mid,f[rt].ls,head);
    else Update2(mid+1,r,f[rt].rs,head);
}
int Query(int l,int r,int rt1,int rt2,int k) {
    if (l==r) return k*l;
    int mid=l+r>>1,siz=f[f[rt1].ls].siz+f[f[rt2].ls].siz;
    if (siz>=k) return Query(l,mid,f[rt1].ls,f[rt2].ls,k);
    else return f[f[rt1].ls].sum+f[f[rt2].ls].sum+Query(mid+1,r,f[rt1].rs,f[rt2].rs,k-siz);
}
signed main(void) {
    int i,x,l,r,sum=0;
    read(n);
    read(m);
    for (i=1; i<=n; i++) read(a[i]),suf[i]=suf[i-1]+a[i];
    for (i=1; i<=n; i++) {
        root[i]=Update(1,n,root[i-1],a[i]);
    }
    for (i=1; i<=m; i++) {
        read(x);read(l);read(r);sum+=x;
        Update2(1,n,rota,x);
        int tmp1=suf[l-1]+sum-Query(1,n,root[l-1],rota,i);
        int tmp2=suf[r]+sum-Query(1,n,root[r],rota,i);
        printf("%lld\n",tmp2-tmp1);
    }
    return 0;
}