【题解】Subsequence [SWTR-05] [P]

【题解】Subsequence [SWTR-05] [P]

传送门:\(\text{Subsequence [SWTR-05] [P]}\)

【题目描述】

给出一个长为 \(n\) \((n\leqslant 2*10^5)\) 的序列 \(a\) \((a_i\leqslant 10^9)\) 。有 \(m\) \((m\leqslant 2*10^5)\) 次询问,每次询问给出 \(L,R,V\),求 \(\sum_{L\leqslant i\leqslant j\leqslant R}[\max(i,j) \leq V]\left(\sum_{k=i}^{j}a_k\right)\),其中 \(\max(i,j)\) 表示区间 \([i,j]\) 的最大值。答案对 \(2^{32}\) 取模。

【分析】

先把 \(a_i\) 从大到小排好序,然后对询问离线,按 \(V\) 从大到小依次处理。

对于每次询问,大于 \(V\) 的点将原序列剖成了若干个连续小区间。把连续小区间与 \([L,R]\) 取并,得到的连续段的所有子区间均可对答案产生贡献,分别计算这些段的贡献再求和即为答案。

连续段显然可以用平衡树维护(类似 \(\text{ODT}\) 的感觉。只是这里只保留了值小于等于 \(V\) 的段),每次加入点 \((i,a_i)\) 就相当于把一个连续段剖成了两半。询问时对于完全包含在 \([L,R]\) 以内的段就在 \(\text{Spaly}\) 上维护一下节点权值和,两个端点所在段则单独计算。

考虑如何快速计算一个连续段 \([l,r]\) 的贡献,也就是这个柿子: \(calc(l,r)=\sum_{i=l}^{r}\sum_{j=i}^{r}S(j)-S(i-1)\),其中 \(S\)\(a\) 的前缀和。

化简得:\(calc(l,r)=\left(\sum_{j=l}^{r}S(j)(j-l+1)\right)-\left(\sum_{i=l}^{r}S(i-1)(r-i+1)\right)\)

预处理一个 \(S_1(n)=\sum_{i=1}^{n}iS(i),\) \(S_2(n)=\sum_{i=1}^{n}S(i)\),显然可以 \(O(1)\) 得到上面的柿子。

时间复杂度:\(O((n+m)\log n)\)

【Code】

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<queue>
#define LL unsigned int
#define Re register LL
using namespace std;
const int N=2e5+10;
LL n,T,A[N],S[N],S1[N],S2[N],Ans[N];
struct QAQ{LL v,l,r,id;inline bool operator<(const QAQ &O)const{return v>O.v;}}a[N],Q[N];
inline void in(Re &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}
inline void print(Re x){if(x>9)print(x/10);putchar(x%10+'0');}
inline LL ask1(Re l,Re r){return l<=r&&r>=1?S1[r]-(l?S1[l-1]:0):0;}
inline LL ask2(Re l,Re r){return l<=r&&r>=1?S2[r]-(l?S2[l-1]:0):0;}
inline LL calc(Re l,Re r){return l<=r?ask1(l,r)-(l-1)*ask2(l,r)-r*ask2(l-1,r-1)+ask1(l-1,r-1):0;}//计算原序列中区间[l,r]的贡献
struct Splay{
    #define pl (tr[p].ps[0])
    #define pr (tr[p].ps[1])
    #define pf (tr[p].fa)
    #define pv (tr[p].v)
    LL O,root,Q[N];queue<LL>D;
    struct QAQ{LL l,r,v,S,rr,fa,size,ps[2];}tr[N];
    inline void pushup(Re p){
        tr[p].S=tr[pl].S+tr[pr].S+pv;
        tr[p].rr=pr?tr[pr].rr:tr[p].r;
        tr[p].size=tr[pl].size+tr[pr].size+1;
    }
    inline LL which(Re p){return tr[pf].ps[1]==p;}
    inline void connect(Re p,Re fa,Re o){tr[pf=fa].ps[o]=p;}
    inline void rotate(Re p){
        Re fa=pf,fas=which(p);
        Re pa=tr[fa].fa,pas=which(fa);
        Re x=tr[p].ps[fas^1];
        connect(x,fa,fas),connect(fa,p,fas^1),connect(p,pa,pas);
        pushup(fa),pushup(p);
    }
    inline void splay(Re p,Re to){
        for(Re fa;pf!=to;rotate(p))
            if(tr[fa=pf].fa!=to)rotate(which(p)==which(fa)?fa:p);
        if(!to)root=p;
    }
    inline void CL(Re p){
        tr[p].size=tr[p].S=tr[p].l=tr[p].r=tr[p].rr=pv=pf=pl=pr=0;
    }
    inline LL New(){
        Re p;
        if(D.empty())p=++O;
        else p=D.front(),D.pop();
        CL(p),tr[p].size=1;return p;
    }
    inline void build0(Re &p,Re l,Re r){//建初始区间{1,n}时在下面挂一个{0,0}和{n+1,n+1},方便后面split
        p=New(),tr[p].l=l,tr[p].r=r,pv=calc(l,r);
        pl=New(),tr[pl].l=tr[pl].r=0,tr[pl].v=0,tr[pl].fa=p,pushup(pl);
        pr=New(),tr[pr].l=tr[pr].r=n+1,tr[pr].v=0,tr[pr].fa=p,pushup(pr);
        pushup(p);
    }
    inline void build(Re &p,Re l,Re r){
        p=New(),tr[p].l=l,tr[p].r=r,pv=calc(l,r),pushup(p);
    }
    inline LL find(Re p,Re K){
        if(K<=tr[pl].size)return find(pl,K);
        return K<=tr[pl].size+1?p:find(pr,K-tr[pl].size-1);
    }
    inline LL split(Re L,Re R){//在Spaly维护的序列上获取区间[L,R]
        Re p=find(root,L-1),q=find(root,R+1);
        splay(p,0),splay(q,p);return tr[q].ps[0];
    }
    inline void insert(Re st,Re l,Re r){//在Spaly维护的序列上第st个点后插入节点{l,r,calc(l,r)}
        Re rt=0;build(rt,l,r);
        Re p=find(root,st),q=find(root,st+1);
        splay(p,0),splay(q,p);
        connect(rt,q,0),pushup(q),pushup(p);
    }
    inline void erase(Re pos){//删除Spaly维护的序列上第pos个节点
        Re p=split(pos,pos),fa=pf;
        tr[fa].ps[0]=pf=0,D.push(p),CL(p);
        pushup(fa),pushup(tr[fa].fa);
    }
    inline LL ask(Re L,Re R){return L<=R?tr[split(L,R)].S:0;}
    inline LL getpos(Re p,Re x){//寻找x所在连续段在Splay上的编号,如果找不到就返回其后面第一个连续段
        if(x<tr[p].l)return x<=tr[pl].rr?getpos(pl,x):p;
        return (x>=tr[p].l&&x<=tr[p].r)?p:getpos(pr,x);
    }
}T1;
struct QWQ{LL l,r,p,pos;};
inline QWQ get(Re x){
    QWQ a;a.p=T1.getpos(T1.root,x),T1.splay(a.p,0);
    a.l=T1.tr[a.p].l,a.r=T1.tr[a.p].r,a.pos=T1.tr[T1.tr[a.p].ps[0]].size+1;
    return a;
}
inline void add(Re x){//加入点x,将一个连续段剖成两半
    QWQ a=get(x);
    T1.erase(a.pos),--a.pos;
    if(a.l<x)T1.insert(a.pos,a.l,x-1),++a.pos;
    if(x<a.r)T1.insert(a.pos,x+1,a.r);
}
inline void updata(QWQ &a){
    a.p=T1.find(T1.root,a.pos),a.l=T1.tr[a.p].l,a.r=T1.tr[a.p].r;
}
inline LL ask(Re L,Re R){//查询L,R
    if(T1.tr[T1.root].size==2)return 0;//没有连续段了
    QWQ bl=get(L),br=get(R);LL ans=0;
    if(R<br.l)--br.pos,updata(br);//如果返回了R后面第一个连续段,稍微处理下
    Re flag1=(L>=bl.l&&L<=bl.r),flag2=(R>=br.l&&R<=br.r);//flag1,flag2分别表示左右端点是否在连续段中间
    if(flag1&&flag2&&bl.pos==br.pos)return calc(L,R);//同一连续段
    if(flag1)ans+=calc(L,bl.r),++bl.pos,updata(bl);//左端点所在连续段被割断
    if(flag2)ans+=calc(br.l,R),--br.pos,updata(br);//右端点所在连续段被割断
    ans+=T1.ask(bl.pos,br.pos);//中间完全包含的连续段直接求和
    return ans;
}
int main(){
//    freopen("123.txt","r",stdin);
    in(n),in(T);
    for(Re i=1;i<=n;++i)in(A[i]),S[i]=S[i-1]+A[i],S1[i]=S1[i-1]+S[i]*i,S2[i]=S2[i-1]+S[i],a[i].v=A[i],a[i].id=i;
    for(Re i=1;i<=T;++i)in(Q[i].l),in(Q[i].r),in(Q[i].v),Q[i].id=i;
    sort(a+1,a+n+1),sort(Q+1,Q+T+1);
    T1.build0(T1.root,1,n);
    for(Re i=1,j=0;i<=T;++i){
        while(j<n&&a[j+1].v>Q[i].v)add(a[++j].id);
        Ans[Q[i].id]=ask(Q[i].l,Q[i].r);
    }
    for(Re i=1;i<=T;++i)print(Ans[i]),putchar('\n');
}

-----若要转载请私信作者获得许可并在文首标出转载来源-----

posted @ 2020-07-05 11:26  辰星凌  阅读(240)  评论(3编辑  收藏  举报