BZOJ4826 HNOI2017 影魔
题目大意
给定一个长为$N$的排列$A$,给定$p_1,p_2$,对于点对$i,j(i<j)$当$i+1=j$或$\forall k(i<k<j)$不存在$A_k<\min\{A_i,A_j\}$,则这一点对对答案的贡献是$p_1$,若$\min\{A_i,A_j\}<\max\{A_k\}(i<k<j)<\max\{A_i,A_j\}$。那么这一点对对答案的贡献是$p_2$。
一共有$m$组询问,每次询问给定区间$[l,r]$求仅考虑$l\leq i< j\leq r$的点对,求其贡献和。
题解
点对对答案的贡献两种,一种是$(i,i+1)$,每一个询问可以$O(1)$算,就先不管它。
另一种$(i,j)(j>i+1)$,一定存在唯一$k$使得$A_k$是$A_{[i+1,j-1]}$中最大的。
考虑枚举这个$k$,分别找到左右侧距离$k$最近的大于$A_k$的
如果$i,j$均找不到,那么无贡献,无需考虑。
如果$\min\{A_i,A_j\}<\max\{A_k\}(i<k<j)<\max\{A_i,A_j\}$,如果$i$出现在询问$[l,r]$中,那么一定有$p_2\times|[l,r]\cap[k+1,j-1]|$的贡献
同理,如果$j$出现在询问$[l,r]$中,那么一定有$p_2\times|[l,r]\cap[i+1,k-1]|$的贡献。
如果$i,j$均出现在$[l,r]$中,那么这个选$i,j$还会有额外$p_1$的贡献,这个可以等价于如果$i$出现在$[l,r]$中那么一定会有$p_1\times|[l,r]\cap[r,r]|$的贡献。
这样一来每一组点对$i,j$,它的贡献一定恰好会被$\max\{A_k\}(i<k<j)$统计到,对于一个一组询问只需要求$[l,r]$包含的点对区间$[l,r]$产生的贡献之和即可。
可以先用单调栈预处理每一个$k$的$i,j$,把一次形如 如果$pos$在区间$[l,r]$中那么对$[tl,tr]$每个位置$+num$看作一次修改。
对于每一个询问$[l,r]$,把它看做$pos\in[1,l-1]$中$[l,r]$的区间和与$pos\in[1,r]$中$[l,r]$的区间和之差,再加上$(i,i+1)$的贡献。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 200020 using namespace std; namespace IO{ const int BS=(1<<21)+5; int Top=0; char Buffer[BS],OT[BS],*OS=OT,*HD,*TL,SS[20]; const char *fin=OT+BS-1; char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} void flush(){fwrite(OT,1,OS-OT,stdout);} void Putchar(char c){*OS++ =c;if(OS==fin)flush(),OS=OT;} void write(LL x){ if(!x){Putchar('0');return;} if(x<0) x=-x,Putchar('-'); while(x) SS[++Top]=x%10,x/=10; while(Top) Putchar(SS[Top]+'0'),--Top; } int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } } using namespace IO; int n,m,T,m1,m2,p[M],K; int S[M],top,od[M],cnt,rt[M],last[M],nxt[M],tot[M<<2]; LL sum[M<<2],ans[M]; struct mdf{int ps,LS,RS,num;}d[M<<2]; struct qu{int pos,fh,id,tl,tr;}t[M<<1]; bool cmp(mdf i,mdf j){return i.ps<j.ps;} bool cmpt(qu i,qu j){return i.pos<j.pos;} #define calc(l1,r1,l2,r2) (min(r1,r2)-max(l1,l2)+1) void add(int x,int l,int r,int ls,int rs,int dt){ if(ls<=l&&r<=rs){tot[x]+=dt;return;} int mid=((l+r)>>1); sum[x]+=(LL)calc(l,r,ls,rs)*(LL)dt; if(ls<=mid) add(x<<1,l,mid,ls,rs,dt); if(rs>mid) add(x<<1|1,mid+1,r,ls,rs,dt); } LL qry(int x,int l,int r,int ls,int rs){ LL res=(LL)calc(l,r,ls,rs)*(LL)tot[x]; if(ls<=l&&r<=rs){return res+sum[x];} int mid=((l+r)>>1); if(ls<=mid) res+=qry(x<<1,l,mid,ls,rs); if(rs>mid) res+=qry(x<<1|1,mid+1,r,ls,rs); return res; } int main(){ freopen("c.in","r",stdin); n=read(),T=read(),m1=read(),m2=read(); for(int i=1;i<=n;i++) p[i]=read(),od[i]=i; for(int i=1;i<=n;i++){ while(top&&p[S[top]]<p[i]) top--; last[i]=S[top],S[++top]=i; } top=0; for(int i=n;i;--i){ while(top&&p[S[top]]<p[i]) top--; nxt[i]=S[top],S[++top]=i; if(!nxt[i]) nxt[i]=n+1; } for(int i=1;i<=n;i++){ if(last[i]&&i+1<nxt[i]) d[++m]=mdf{last[i],i+1,nxt[i]-1,m2}; if(nxt[i]<=n&&i-1>last[i]) d[++m]=mdf{nxt[i],last[i]+1,i-1,m2}; if(last[i]>0&&nxt[i]<=n) d[++m]=mdf{nxt[i],last[i],last[i],m1}; } sort(d+1,d+m+1,cmp); for(int i=1;i<=T;i++){ int t1=read(),t2=read(); if(t1>1) t[++K]=qu{t1-1,-1,i,t1,t2}; t[++K]=qu{t2,1,i,t1,t2},ans[i]=(LL)m1*(LL)(t2-t1); } sort(t+1,t+K+1,cmpt); for(int i=1,nw=1;i<=K;i++){ while(nw<=m&&d[nw].ps<=t[i].pos) add(1,1,n,d[nw].LS,d[nw].RS,d[nw].num),nw++; ans[t[i].id]+=(LL)t[i].fh*qry(1,1,n,t[i].tl,t[i].tr); } for(int i=1;i<=T;i++) write(ans[i]),Putchar('\n'); flush(); return 0; }
当然这道题也有在线做法,你可以用大量的时间和空间使用主席树区间修改标记永久化来做到。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define LL long long #define M 200020 using namespace std; namespace IO{ const int BS=(1<<21)+5; int Top=0; char Buffer[BS],OT[BS],*OS=OT,*HD,*TL,SS[20]; const char *fin=OT+BS-1; char Getchar(){if(HD==TL){TL=(HD=Buffer)+fread(Buffer,1,BS,stdin);} return (HD==TL)?EOF:*HD++;} void flush(){fwrite(OT,1,OS-OT,stdout);} void Putchar(char c){*OS++ =c;if(OS==fin)flush(),OS=OT;} void write(LL x){ if(!x){Putchar('0');return;} if(x<0) x=-x,Putchar('-'); while(x) SS[++Top]=x%10,x/=10; while(Top) Putchar(SS[Top]+'0'),--Top; } int read(){ int nm=0,fh=1; char cw=Getchar(); for(;!isdigit(cw);cw=Getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=Getchar()) nm=nm*10+(cw-'0'); return nm*fh; } } using namespace IO; int n,m,T,m1,m2,p[M],L[M*100],R[M*100]; int S[M],top,od[M],cnt,rt[M],last[M],nxt[M]; LL sum[M*100],tot[M*100]; struct qs{ int ps,LS,RS,num; qs(){} qs(int _ps,int _LS,int _RS,int _num){ps=_ps,LS=_LS,RS=_RS,num=_num;} }q[M<<2]; bool cmp(qs i,qs j){return i.ps<j.ps;} #define calc(l1,r1,l2,r2) (min(r1,r2)-max(l1,l2)+1) void add(int &x,int y,int l,int r,int ls,int rs,int dt){ if(rs<ls) return; x=++cnt,sum[x]=sum[y],tot[x]=tot[y],L[x]=L[y],R[x]=R[y]; if(ls<=l&&r<=rs){tot[x]+=dt;return;} int mid=((l+r)>>1); sum[x]+=(LL)calc(l,r,ls,rs)*(LL)dt; if(ls<=mid) add(L[x],L[y],l,mid,ls,rs,dt); if(rs>mid) add(R[x],R[y],mid+1,r,ls,rs,dt); } LL qry(int x,int y,int l,int r,int ls,int rs){ LL res=(LL)calc(l,r,ls,rs)*(tot[x]-tot[y]); if(ls<=l&&r<=rs){return res+sum[x]-sum[y];} int mid=((l+r)>>1); if(ls<=mid) res+=qry(L[x],L[y],l,mid,ls,rs); if(rs>mid) res+=qry(R[x],R[y],mid+1,r,ls,rs); return res; } int main(){ n=read(),T=read(),m1=read(),m2=read(); for(int i=1;i<=n;i++) p[i]=read(),od[i]=i; for(int i=1;i<=n;i++){ while(top&&p[S[top]]<p[i]) top--; last[i]=S[top],S[++top]=i; } top=0; for(int i=n;i;--i){ while(top&&p[S[top]]<p[i]) top--; nxt[i]=S[top],S[++top]=i; if(!nxt[i]) nxt[i]=n+1; } for(int i=1;i<=n;i++){ if(last[i]) q[++m]=qs(last[i],i+1,nxt[i]-1,m2); if(nxt[i]<=n) q[++m]=qs(nxt[i],last[i]+1,i-1,m2); if(last[i]>0&&nxt[i]<=n) q[++m]=qs(nxt[i],last[i],last[i],m1); } sort(q+1,q+m+1,cmp); for(int now=1,i=1;i<=n;i++){ for(rt[i]=rt[i-1];now<=m&&q[now].ps<=i;now++) add(rt[q[now].ps],rt[q[now].ps],1,n,q[now].LS,q[now].RS,q[now].num); } while(T--){ int t1=read(),t2=read(); LL ans; ans=qry(rt[t2],rt[t1-1],1,n,t1,t2); ans+=(LL)(t2-t1)*(LL)m1,write(ans),Putchar('\n'); } flush(); return 0; }