[BZOJ2006][NOI2010]超级钢琴

BZOJ
Luogu

sol

有一个很暴力的想法,就是把所有合法的状态丢到一个堆里面,然后依次取出最大值。这样的话时间是\(O(n^2logn)\),空间是\(O(n^2)\)
我们考虑优化这个过程。对于右端点相同的所有合法区间,我们只在堆中保留最大的一个,在取出这一个以后再丢入次大的,取出次大的再丢入次次大的,以此类推。这样可以保证正确性,同时空间被优化到了\(O(n)\)
现在我们考虑怎么查这个次次次次(这里有若干个次)大值。
首先把区间和转化成前缀和相减,因为我们固定了右端点,那么最大值就是对应了前缀和中减去的那个值最小。而第k大值刚好对应减去的那个数第k小,同时还要保证在一定区间范围内(L、R的限制)。所以离散前缀和以后主席树即可。
注意前缀和减去的部分可以是0,所以0也要被离散进去。

code

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
const int N = 500005;
int gi()
{
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
struct node{
	int i,k,val;
	bool operator < (const node &yyb) const
		{return val<yyb.val;}
};
struct prisident_tree{int ls,rs,num;}t[N*30];
int n,K,L,R,a[N],o[N],len,rt[N],tot;ll ans;
priority_queue<node>Q;
void modify(int &x,int l,int r,int p)
{
	t[++tot]=t[x];
	t[x=tot].num++;
	if (l==r) return;
	int mid=l+r>>1;
	if (p<=mid) modify(t[x].ls,l,mid,p);
	else modify(t[x].rs,mid+1,r,p);
}
int query(int A,int B,int l,int r,int k)
{
	if (l==r) return l;
	int sum=t[t[A].ls].num-t[t[B].ls].num,mid=l+r>>1;
	if (k<=sum) return query(t[A].ls,t[B].ls,l,mid,k);
	else return query(t[A].rs,t[B].rs,mid+1,r,k-sum);
}
int main()
{
	n=gi();K=gi();L=gi();R=gi();n++;
	for (int i=2;i<=n;i++) a[i]=gi();
	for (int i=1;i<=n;i++) o[++len]=a[i]=a[i]+a[i-1];
	sort(o+1,o+len+1);len=unique(o+1,o+len+1)-o-1;
	for (int i=1;i<=n;i++) a[i]=lower_bound(o+1,o+len+1,a[i])-o;
	for (int i=1;i<=n;i++) rt[i]=rt[i-1],modify(rt[i],1,len,a[i]);
	for (int i=L+1;i<=n;i++) Q.push((node){i,1,o[a[i]]-o[query(rt[i-L],rt[max(0,i-R-1)],1,len,1)]});
	while (K--)
	{
		node tmp=Q.top();Q.pop();
		ans+=tmp.val;
		if (tmp.k==min(R-L+1,tmp.i-L)) continue;
		tmp.k++;
		tmp.val=o[a[tmp.i]]-o[query(rt[tmp.i-L],rt[max(0,tmp.i-R-1)],1,len,tmp.k)];
		Q.push(tmp);
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2018-01-23 16:13  租酥雨  阅读(250)  评论(0编辑  收藏  举报