bzoj2006: [NOI2010]超级钢琴
也是一道好题呀。
首先先把它转换成前缀和应该都想到了。
然后用st表把s的最小值的id存下来,那么想一想,对于当前的位置rid,s[rid]-s[lid](其中rid-R+1<=lid<=rid-L+1)就是一个和弦的方案,用优先队列维护s[rid]-s[lid]最大值,跑k次。
但是当当前这个lid已用时,我们怎样找到对于rid区间次大的s呢
我们在结构体再记录一个区间fl,fr表示当前这个lid是在fl到fr这个区间里的,当我们的堆顶找到了它,在记录答案的同时,把区间分裂成fl~lid-1和lid+1~fr两部分,再次进队列就可以了,相当于筛去了lid这个位置。
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<queue> using namespace std; const int inf=2147483647; typedef long long LL; int Bin[30]; int s[510000],id[21][500100]; int getmin(int l,int r) { if(l<1)l=1; int ret=-1; for(int j=20;j>=0;j--) if(r-l+1>=Bin[j]) { if(ret==-1||s[ret]>s[id[j][l]]) ret=id[j][l]; l+=Bin[j]; } return ret; } struct node { int fl,fr,lid,rid; friend bool operator <(node n1,node n2) { return (s[n1.rid]-s[n1.lid])<(s[n2.rid]-s[n2.lid]); } }; priority_queue<node>q; int main() { Bin[0]=1;for(int i=1;i<=25;i++)Bin[i]=Bin[i-1]*2; int n,K,L,R; scanf("%d%d%d%d",&n,&K,&L,&R); s[1]=0;int x; for(int i=1;i<=n;i++) { scanf("%d",&x); s[i+1]=s[i]+x; } for(int i=1;i<=n;i++)id[0][i]=i; for(int j=1;j<=20;j++) for(int i=1;(i+Bin[j]-1)<=n;i++) if(s[id[j-1][i]]>s[ id[j-1][i+Bin[j-1]] ]) id[j][i]=id[j-1][i+Bin[j-1]]; else id[j][i]=id[j-1][i]; for(int i=L;i<=n;i++) { node nn; nn.fl=max(1,i-R+1); nn.fr=i-L+1; nn.lid=getmin(nn.fl,nn.fr); nn.rid=i+1; q.push(nn); } LL ans=0; while(K--) { node nn=q.top();q.pop(); ans+=LL(s[nn.rid]-s[nn.lid]); node tt; if(nn.lid+1<=nn.fr) { tt.fl=nn.lid+1; tt.fr=nn.fr; tt.lid=getmin(tt.fl,tt.fr); tt.rid=nn.rid; q.push(tt); } if(nn.lid-1>=nn.fl) { tt.fl=nn.fl; tt.fr=nn.lid-1; tt.lid=getmin(tt.fl,tt.fr); tt.rid=nn.rid; q.push(tt); } } printf("%lld\n",ans); return 0; }
pain and happy in the cruel world.