BZOJ2006超级钢琴
单独放题解了= =
题意:有n个整数,然后从中间选出k段不同的、长度在[L,R]之间的区间,使得所有区间的区间和加起来最大。n,k<=50w
题解:思路对于我这种想不到的弱逼还是蛮屌的。首先看上去很有堆的感觉,但是明显把不能枚举状态,于是考虑如何优化。。那可以维护对于每个点x以它为左端点的三元组[L,R,w],表示从x到w的区间和是右端点在L到R里面最大的,那么就可以每次把n个左端点里面最大的加进来,然后分成[L,w-1,w1]以及[w+1,R,w2]加入堆中。而求w就是先搞出一个前缀和s[i],然后询问L到R的s[i]最大值,即RMQ问题,我用了倍增解,然后复杂度就是差不多O(nlog^2 n)。。
#include <iostream> #include <cstring> #include <cmath> #include <cstdio> #include <algorithm> #include <set> using namespace std; #define N 500050 #define M 30 #define ll long long #define INF 0x7f7f7f7f struct Point { int le,ri,bg,wz;ll t; bool operator < (const Point &x) const { return t>x.t||(t==x.t&&le>x.t)||(t==x.t&&le==x.le&&bg>x.bg); } }; multiset <Point> li; set <Point> :: iterator it; int n,m,st[N][M],L,R; ll s[N],ans=0; inline int Read() { int x=0;char y;bool z=0; do y=getchar(),z|=y=='-'; while (y<'0'||y>'9'); do x=x*10+y-'0',y=getchar(); while (y>='0'&&y<='9'); return z?-x:x; } int Find(int x,int y,int z) { if (x>y) return -INF; if (y-x+1==(1 << z)) return st[x][z]; if (y-x+1<(1 << z)) return Find(x,y,z-1); int i=Find(x+(1 << z),y,z-1); return s[i]>s[st[x][z]]?i:st[x][z]; } inline Point Make(int x,int y,int z) { Point i; i.bg=x;i.le=y;i.ri=z;i.wz=Find(y,z,20); i.t=s[i.wz]-s[i.bg-1]; return i; } int main() { int i,j,k,l;Point q,w,e; memset(s,0,sizeof(s));memset(st,0,sizeof(st)); scanf("%d%d%d%d",&n,&m,&L,&R); for (i=1;i<=n;i++) s[i]=s[i-1]+Read(),st[i][0]=i; for (i=1;(1 << i)<=n;i++) for (j=1;j<=n-(1 << i)+1;j++) st[j][i]=s[st[j+(1 << (i-1))][i-1]]>s[st[j][i-1]]? st[j+(1 << (i-1))][i-1]:st[j][i-1]; for (i=1;i<=n-L+1;i++) li.insert(Make(i,i+L-1,min(i+R-1,n))); while (m--) { it=li.begin();q=*it;li.erase(it);ans+=q.t; if (q.wz!=q.le) li.insert(Make(q.bg,q.le,q.wz-1)); if (q.wz!=q.ri) li.insert(Make(q.bg,q.wz+1,q.ri)); } cout <<ans<<endl; return 0; }