【51Nod】1672 - 区间交(线段树 & 贪心)

点击打开题目

基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
 收藏
 取消关注
小A有一个含有n个非负整数的数列与m个区间,每个区间可以表示为li,ri。
它想选择其中k个区间, 使得这些区间的交的那些位置所对应的数的和最大。(具体可以参照样例)

在样例中,5个位置对应的值分别为1,2,3,4,6,那么选择[2,5]与[4,5]两个区间的区间交为[4,5],它的值的和为10。
Input
第一行三个数n,k,m(1<=n<=100000,1<=k<=m<=100000)。
接下来一行n个数ai,表示小A的数列(0<=ai<=10^9)。
接下来m行,每行两个数li,ri,表示每个区间(1<=li<=ri<=n)。
Output
一行表示答案
Input示例
5 2 3
1 2 3 4 6
4 5
2 5
1 4
Output示例
10



想明白以后确实是不难的,就是前期想着不好想。为了证明自己真的是理解了,把数据按左端点排序自己拍了一遍。


解释一下这个题:

我们首先把左端点从小到大排序(掌握以后,这个排序不论是排右端点、从大到小都可以做的),然后我们依次枚举左端点,我们把当前枚举的左端点 l 当做区间交的左端点,那么很明显,其他的区间的左端点肯定是要小于 l ,那么也就是说,前 k - 1 个端点不用枚举。

接下来就是数据的处理了,我们枚举了左端点,然后找右端点的时候要尽量靠右,这个右端点该怎么找呢?我们用一个线段树记录右端点,当我们枚举第 x 个区间的时候,后面的区间的右端点都是干扰的,我们就先不让它加入线段树,当然,前面的端点都要在线段树里(当时是这一点没想到),然后在这些端点中找最靠右的右端点,然后算前缀和更新一下ans。

然后我们要处理 x+1 区间了,我们把 x+1 的右端点加入线段数,重复前面的操作即可。


代码如下:(写了详细的注释)

#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
#define MAX 100000
#define L o<<1
#define R o<<1|1
LL sum[MAX+5];
struct node
{
	int st,endd;
}a[MAX+5];
struct Tree
{
	int l,r;
	int cover;
}tree[MAX<<2];
bool cmp(node a,node b)
{
	return a.st < b.st;
}
void PushUp(int o)
{
	tree[o].cover = tree[L].cover + tree[R].cover;
}
void Build(int o,int l,int r)
{
	tree[o].l = l;
	tree[o].r = r;
	if (l == r)
	{
		tree[o].cover = 0;		//覆盖点全部初始化为0 
		return;
	}
	int mid = (l + r) >> 1;
	Build(L , l , mid);
	Build(R , mid + 1 , r);
	PushUp(o);
}
void UpDate(int o,int x)		//x位置cover值加一 
{
	if (tree[o].l == tree[o].r)
	{
		tree[o].cover++;
		return;
	}
	int mid = (tree[o].l + tree[o].r) >> 1;
	if (mid >= x)
		UpDate(L , x);
	else
		UpDate(R , x);
	PushUp(o);
}
int Query(int o,int x)		//满足条件的最靠右的端点下标 
{
	if (tree[o].l == tree[o].r)
		return tree[o].l;
	if (tree[R].cover >= x)		//优先从右树中找满足条件的
		return Query(R , x);
	else
		return Query(L , x-tree[R].cover);		//右边不够剩下的点从左树找 
}
int main()
{
	int n,k,m;
	int t;
	scanf ("%d %d %d",&n,&k,&m);
	sum[0] = 0;
	for (int i = 1 ; i <= n ; i++)
	{
		scanf ("%d",&t);
		sum[i] = sum[i-1] + t;
	}
	for (int i = 1 ; i <= m ; i++)
		scanf ("%d %d",&a[i].st,&a[i].endd);
	sort (a+1 , a+1+m , cmp);		//对左端点排序 
	Build(1,1,n);
	for (int i = 1 ; i <= k ; i++)		//前k-1个数的左端点不用枚举,直接更新其右端点即可 
		UpDate(1,a[i].endd);
	LL ans = 0;
	a[m+1].endd = 1;		//随便赋值一个,防止最后一次循环出问题 
	for (int i = k ; i <= m ; i++)		//从第k个点开始
	{
		int pos = Query(1,k);
		if (pos >= a[i].st)
			ans = max (ans , sum[pos] - sum[a[i].st-1]);
		UpDate(1,a[i+1].endd);		//将下一点的右端点加入,即其cover值加一 
	}
	printf ("%lld\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值