NOI2010 超级钢琴
题目大意
给定一个长为$N$的序列,求前$K$大的长度$\in[L,R]$子区间和的和。$N,M\leq 5\times 10^5$,保证有解。
题解
考虑由于$K$可以认为与$N$同阶,可以考虑枚举所有的前$K$大子区间累计起来。
先求出原序列的前缀和$S$,对于每一个固定的右端点$i$,区间最大值左端点只跟$\min\{S_x\}(i-R\leq x\leq i-L)$有关。
我们可以预处理$S$区间最小值的位置,将所有的右端点和其对应左端点可选范围封装起来以答案为关键字存进大根堆中,每次从堆顶取出一个最大的,将左端点可选区间中最小的$S$取出来加进答案中,再把这个区间取出来的左端点去掉,分裂成两个区间,再分别塞进堆中即可。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<queue> #define LL long long #define M 500030 using namespace std; const int BS=(1<<20); char Buffer[BS],*head,*tail; char Getchar(){if(head==tail){tail=(head=Buffer)+fread(Buffer,1,BS,stdin);} return *head++;} 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; } int n,m,minn,maxn,G[M][20],lg[M],p[M];LL ans; struct data{ int ls,rs,K,rt;data(){}data(int _ls,int _rs,int _K,int _rt){ls=_ls,rs=_rs,K=_K,rt=_rt;} bool operator<(const data&ot)const{return p[rt]-p[K]<p[ot.rt]-p[ot.K];} }; priority_queue<data> Q; int rmq(int L,int R){ int k=lg[R-L+1]; int t1=G[L][k],t2=G[R-(1<<k)+1][k]; if(p[t1]<p[t2]) return t1; return t2; } void add(int x,int L,int R){Q.push(data(L,R,rmq(L,R),x));} int main(){ n=read(),m=read(),minn=read(),maxn=read(),lg[0]=-1; for(int i=1;i<=n;i++) p[i]=p[i-1]+read(),G[i][0]=i,lg[i]=lg[i>>1]+1; for(int k=1;k<=lg[maxn-minn+1];k++){ for(int i=0;i+(1<<k)-1<=n;i++){ int t1=G[i][k-1],t2=G[i+(1<<(k-1))][k-1]; if(p[t1]<p[t2]) G[i][k]=t1; else G[i][k]=t2; } } for(int i=minn;i<=n;i++) add(i,max(0,i-maxn),i-minn); while(m--){ data tmp=Q.top(); Q.pop(),ans+=p[tmp.rt]-p[tmp.K]; if(tmp.ls<tmp.K) add(tmp.rt,tmp.ls,tmp.K-1); if(tmp.rs>tmp.K) add(tmp.rt,tmp.K+1,tmp.rs); } printf("%lld\n",ans); return 0; }