BZOJ 2006: [NOI2010]超级钢琴
题目大意:
一个区间的价值为区间内所有数的和。
求序列中长度在L至R的区间中价值前k大的区间的价值和。
题解:
一个区间价值用s[r]-s[l]来表示,s为前缀和。假设固定了右端点,则可以通过st表来确定左端点的最优位置。对于所有的右端点,我们将他们扔进堆里就能找出最大值来。
然后对于一个右端点确定了一个左端点,之后对于这个右端点这个左端点就不能选了,所以把原来区间断成两端,再扔进堆里。重复k次就可以了。
代码:
#include<cstdio> #include<algorithm> #include<queue> #define ma make_pair #define pr pair<int,int> #define prr pair<pr,pr> #define mp make_pair #define fr first #define sc second using namespace std; int n,k,l,r,a[1000005],lg[1000005],st[500005][21],sum[1000005]; priority_queue<prr> q; int calc(int a,int b){ if (sum[a]<sum[b]) return a; else return b; } int query(int a,int b){ if (a>b) return -1; int len=lg[b-a+1]; return calc(st[a][len],st[b-(1<<len)+1][len]); } int main(){ scanf("%d%d%d%d",&n,&k,&l,&r); for (int i=1; i<=n; i++){ scanf("%d",&a[i]); sum[i]=sum[i-1]+a[i]; } for (int i=2; i<=n; i++) lg[i]=lg[i>>1]+1; for (int i=1; i<=n; i++) st[i][0]=i; for (int j=1; (1<<j)<=n; j++) for (int i=0; i+(1<<j)-1<=n; i++) st[i][j]=calc(st[i][j-1],st[i+(1<<j-1)][j-1]); for (int i=l; i<=n; i++) q.push(mp(mp(sum[i]-sum[query(max(i-r,0),i-l)],i),mp(max(i-r,0),i-l))); long long ans=0; for (int i=1; i<=k; i++){ ans+=q.top().fr.fr; int x=q.top().fr.sc,a=q.top().sc.fr,b=q.top().sc.sc,y=query(a,b); q.pop(); int id1=query(a,y-1),id2=query(y+1,b); if (id1!=-1) q.push(mp(mp(sum[x]-sum[id1],x),mp(a,y-1))); if (id2!=-1) q.push(mp(mp(sum[x]-sum[id2],x),mp(y+1,b))); } printf("%lld\n",ans); return 0; }