【题解】Subsequence [SWTR-05] [P]
传送门:( ext{Subsequence [SWTR-05] [P]})
【题目描述】
给出一个长为 (n) ((nleqslant 2*10^5)) 的序列 (a) ((a_ileqslant 10^9)) 。有 (m) ((mleqslant 2*10^5)) 次询问,每次询问给出 (L,R,V),求 (sum_{Lleqslant ileqslant jleqslant R}[max(i,j) leq V]left(sum_{k=i}^{j}a_k ight)),其中 (max(i,j)) 表示区间 ([i,j]) 的最大值。答案对 (2^{32}) 取模。
【分析】
先把 (a_i) 从大到小排好序,然后对询问离线,按 (V) 从大到小依次处理。
对于每次询问,大于 (V) 的点将原序列剖成了若干个连续小区间。把连续小区间与 ([L,R]) 取并,得到的连续段的所有子区间均可对答案产生贡献,分别计算这些段的贡献再求和即为答案。
连续段显然可以用平衡树维护(类似 ( ext{ODT}) 的感觉。只是这里只保留了值小于等于 (V) 的段),每次加入点 ((i,a_i)) 就相当于把一个连续段剖成了两半。询问时对于完全包含在 ([L,R]) 以内的段就在 ( ext{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) ight)-left(sum_{i=l}^{r}S(i-1)(r-i+1) ight))
预处理一个 (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('
');
}