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;
 }