[BZOJ 2006] 超级钢琴
Link:
https://www.lydsy.com/JudgeOnline/problem.php?id=2006
Algorithm:
对于此类区间最值类问题,我们可以通过控制一端不变来寻找当前点的最值,再综合比较
此题中,在求完前缀和后,在左端点确定的情况下,只要寻找前缀和最大的右端点
为了快速寻找最优的右端点位置,我们需要RMQ来进行维护
由于不存在修改操作而只有查询,那么ST List O(1)查询 O(n)修改 的特性就能充分利用
在求出前缀和后用ST list维护区间MAX即可
定义一个四元组(i,L,R,pos),其中,i表示固定下的左端点,L,R表示右端点存在的区间,pos表示右端点此时最优位置
为了不涉及到ST list不支持的删除操作,在选定pos后四元组分为两段:
$(i,L,R,pos)−>(i,L,pos−1,pos′)+(i,pos+1,R,pos′′)$
这样用优先队列每次取出最优解即可
Code:
//by NewErA #include<bits/stdc++.h> using namespace std; typedef long long ll; inline int read() { char ch;int num,f=0; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } struct SP { int i,l,r,pos; }; const int MAXN=500005; int n,k,L,R,a[MAXN],log_2[MAXN],st[MAXN][100]; ll res=0; void init() { log_2[1]=0; for(int i=2;i<=n;i++) { log_2[i]=log_2[i-1]; if(1<<(log_2[i]+1)==i) log_2[i]++; } for(int i=n;i>=1;i--) { st[i][0]=i; for(int j=1;(i+(1<<j)-1)<=n;j++) if(a[st[i][j-1]]>a[st[i+(1<<(j-1))][j-1]]) st[i][j]=st[i][j-1]; else st[i][j]=st[i+(1<<(j-1))][j-1]; } } int Query(int l,int r) { int x=log_2[r-l+1]; if(a[st[l][x]]>a[st[r-(1<<x)+1][x]]) return st[l][x]; else return st[r-(1<<x)+1][x]; } inline bool operator < (SP x,SP y) //运算符重载 { return a[x.pos]-a[x.i-1]<a[y.pos]-a[y.i-1]; } int main() { n=read();k=read();L=read();R=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=2;i<=n;i++) a[i]+=a[i-1]; init(); priority_queue<SP,vector<SP> > Q; for(int i=1;i<=n;i++) if(i+L-1<=n) { int t=min(i+R-1,n); Q.push(SP{i,i+L-1,t,Query(i+L-1,t)}); } while(k--) { SP cur=Q.top();Q.pop(); res+=a[cur.pos]-a[cur.i-1]; if(cur.pos-1>=cur.l) Q.push(SP{cur.i,cur.l,cur.pos-1,Query(cur.l,cur.pos-1)}); if(cur.pos+1<=cur.r) Q.push(SP{cur.i,cur.pos+1,cur.r,Query(cur.pos+1,cur.r)}); } cout << res; return 0; }
1、priority_queue的运算符重载问题(Updating)
2、只需要RMQ的查询操作时尽量用ST List
3、如果删除1个或少量数据的操作难以实现时,考虑将原数据分段,递归式地考虑每一段的情况