【BZOJ】2006: [NOI2010]超级钢琴
【题意】给定长度为n的整数序列,求长度为[L,R]的前k大区间和的和。n,k<=500000。
【算法】堆+贪心+RMQ
【题解】考虑暴力是取所有长度为[L,R]的子串的前k大求和,复杂度O(n^2)。
发现左端点相同的区间[l,r]中,最大的区间和就是最大的sum[r](sum是前缀和数组)。
然后将所以左端点放进堆中,每次取出堆顶之后把第二大的r扔进去,重复k次。
现在的问题是取区间最大的r,第二大的r……区间第k大?用主席树维护即可,复杂度O(n log n)。
还可以用ST表(RMQ)维护,定义三元组(x,l,r)表示取左端点为x,右端点为[l,r]的区间最大sum[r]。
假设对于(x,l,r),取出元素y,就在堆中加入两个三元组(x,l,y-1)和(x,y+1,r)。
复杂度O(n log n)。
#include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int maxn=500010; int n,k,L,R,d[maxn][22],sum[maxn],p[maxn][20],logs[maxn]; struct cyc{ int x,l,r,y; bool operator < (const cyc &a) const {return sum[y]-sum[x-1]<sum[a.y]-sum[a.x-1];} }qp; priority_queue<cyc>q; void RMQ_init() { logs[0]=-1;for(int i=1;i<=n;i++)logs[i]=logs[i>>1]+1; for(int i=1;i<=n;i++){d[i][0]=sum[i];p[i][0]=i;} for(int j=1;(1<<j)<=n;j++) for(int i=1;i+(1<<j)-1<=n;i++) { d[i][j]=max(d[i][j-1],d[i+(1<<(j-1))][j-1]); p[i][j]=d[i][j-1]>d[i+(1<<(j-1))][j-1]?p[i][j-1]:p[i+(1<<(j-1))][j-1]; } } int RMQ(int l,int r) { int K=logs[r-l+1]; return d[l][K]>d[r-(1<<K)+1][K]?p[l][K]:p[r-(1<<K)+1][K]; } int main() { scanf("%d%d%d%d",&n,&k,&L,&R); sum[0]=0; for(int i=1;i<=n;i++){scanf("%d",&sum[i]);sum[i]+=sum[i-1];} RMQ_init(); for(int i=1;i<=n;i++){ if(i+L-1<=n)q.push((cyc){i,i+L-1,min(n,i+R-1),RMQ(i+L-1,min(n,i+R-1))}); } long long ans=0; for(int i=1;i<=k;i++) { qp=q.top();q.pop(); ans+=sum[qp.y]-sum[qp.x-1]; if(qp.y>qp.l)q.push((cyc){qp.x,qp.l,qp.y-1,RMQ(qp.l,qp.y-1)}); if(qp.y<qp.r)q.push((cyc){qp.x,qp.y+1,qp.r,RMQ(qp.y+1,qp.r)}); } printf("%lld",ans); return 0; }