洛谷2048 BZOJ2006 NOI2010 超级钢琴 线段树 堆 贪心

29 篇文章 0 订阅
6 篇文章 0 订阅

题目链接

题意:
给你一个长度为 n n n的序列,一个区间的权值是区间内的权值和,你要选择 k k k个不相同长度在 [ l , r ] [l,r] [l,r]的区间,使得这 k k k个区间总权值和最大。 n , k < = 5 e 5 n,k<=5e5 n,k<=5e5

题解:
算是送我退役的题吧。如果一轮省选之前我做了这个题,可能结果就会不一样了吧,可惜没有如果。

显然是找长度在 [ l , r ] [l,r] [l,r]区间内的最大的 k k k个区间作为答案。那么考虑怎么找这前 k k k大的。我们预处理一个前缀和,这样区间和就变成了前缀和相减的形式。我们的思路是,对于每一个左端点,合法的右端点是一个连续的区间,而对于左端点是 i i i的区间,前缀和相减的形式一定是 s [ j ] − s [ i − 1 ] s[j]-s[i-1] s[j]s[i1],这时候对于一个确定的 i i i s [ i − 1 ] s[i-1] s[i1]是确定的,那么我们只需要找合法区间内一个最大的 s [ j ] s[j] s[j]。那么我们用线段树维护一个区间前缀和的最大值,就可以快速找到每个位置为左端点的权值和最大的一个合法区间了。

而所有可能的区间中最大的一定是在每个区间为左端点求出的最大的区间中产生。我们用一个大根堆来维护这些区间的区间和。然后我们考虑拿出这个最大的区间后,原来的那个左端点还有哪些右端点可能成为最大的区间。我们发现如果原来可行的右端点区间是 [ i , j ] [i,j] [i,j],前缀和最大的位置在 m i d mid mid,那么之后可能的区间是 [ i , m i d − 1 ] [i,mid-1] [i,mid1] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。我们分别再求出这两个区间中前缀和最大的,放入堆中。这样每个区间被取出来之后会放进去两个区间,区间总数还是 O ( k ) O(k) O(k)量级的。

最终复杂度是 O ( k l o g n + k l o n g k ) O(klogn+klongk) O(klogn+klongk),堆和线段树的复杂度是并行的。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,k,L,R,a[500010];
long long ans,s[500010];
struct node
{
	int l,r,i,id;
	long long x;
	node (long long xx=0,int ll=0,int rr=0,int ii=0,int iid=0)
	{
		x=xx;
		l=ll;
		r=rr;
		i=ii;
		id=iid;
	}
};
bool operator < (node x,node y){return x.x<y.x;}
struct tree
{
	int l,r,ji;
	long long mx;
}tr[2000010];
priority_queue<node> q;
inline int read()
{
	int x=0,f=1;
	char s=getchar();
	while(s>'9'||s<'0')
	{
		if(s=='-')
		f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x*f;
}
inline void build(int rt,int l,int r)
{
	tr[rt].l=l;
	tr[rt].r=r;
	if(l==r)
	{
		tr[rt].mx=s[l];
		tr[rt].ji=l;
		return;
	}
	int mid=(l+r)>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	if(tr[rt<<1].mx>tr[rt<<1|1].mx)
	{
		tr[rt].mx=tr[rt<<1].mx;
		tr[rt].ji=tr[rt<<1].ji;
	}
	else
	{
		tr[rt].mx=tr[rt<<1|1].mx;
		tr[rt].ji=tr[rt<<1|1].ji;
	}
}
inline tree query(int rt,int le,int ri)
{
	int l=tr[rt].l,r=tr[rt].r;
	if(le<=l&&r<=ri)
	return tr[rt];
	int mid=(l+r)>>1;
	tree qwq,res;
	res.mx=-1e18;
	if(le<=mid)
	{
		qwq=query(rt<<1,le,ri);
		if(qwq.mx>res.mx)
		res=qwq;
	}
	if(mid+1<=ri)
	{
		qwq=query(rt<<1|1,le,ri);
		if(qwq.mx>res.mx)
		res=qwq;
	}
	return res;
}
int main()
{
	n=read();
	k=read();
	L=read();
	R=read();
	for(int i=1;i<=n;++i)
	{
		a[i]=read();
		s[i]=s[i-1]+a[i];
	}
	build(1,1,n);
	for(int i=0;i<=n-L;++i)
	{
		tree ovo=query(1,i+L,min(n,i+R));
		node qwq(ovo.mx-s[i],i+L,min(n,i+R),ovo.ji,i);
		q.push(qwq);
	}
	while(k)
	{
		node qwq=q.top(),QAQ;
		q.pop();
		ans+=qwq.x;
		tree ovo;
		if(qwq.i+1<=qwq.r)
		{
			ovo=query(1,qwq.i+1,qwq.r);
			QAQ.x=ovo.mx-s[qwq.id];
			QAQ.l=qwq.i+1;
			QAQ.r=qwq.r;
			QAQ.i=ovo.ji;
			QAQ.id=qwq.id;
			q.push(QAQ);
		}
		if(qwq.l<=qwq.i-1)
		{
			ovo=query(1,qwq.l,qwq.i-1);
			QAQ.x=ovo.mx-s[qwq.id];
			QAQ.l=qwq.l;
			QAQ.r=qwq.i-1;
			QAQ.i=ovo.ji;
			QAQ.id=qwq.id;
			q.push(QAQ);
		}
		--k;
	}
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值