区间(贪心做法+线段树优化)

题目

这道题目的话,按 r r r从小到大排序,然后如果对于当前的区间没有满足至少 c i c_i ci个的话,就尽量选右边的没有选过的数字。显而易见是正确的

正确性证明:

对于当前这个区间 i i i而言,如果前面 i − 1 i-1 i1个区间已经是用了最少的数字,且尽量的把数字凑到 i i i区间的 l l l的的话,如果想要让前 i − 1 i-1 i1的区间对 i i i区间的贡献 + 1 +1 +1,那么前 i − 1 i-1 i1个区间必须多用一个数字。

那么我们证明,全部放右边能让前 i i i个区间也满足这样的性质。(其实就是数学归纳法)


首先证明最少,首先前面是已经尽量的靠右边放了,也就是说对于这个区间的贡献是已经最大化了,那么由于其是最小了,所以我们不能让其更小,那么我们就只能求让其贡献变大来减少我们在这个区间放的数字个数,但是由于贡献增加数和区间少放的数字呈 1 : 1 1:1 1:1关系,所以并不会让总数字减少。

那么我们继续证明贡献是最大化的情况,由于按 r r r排序,所以对于第 i + 1 i+1 i+1区间交集部分而言,假设交集部分是 [ x , r ] [x,r] [x,r],如果这个区间的数字都被选了,那么就是最大化的情况,如果没有放满,说明我们可以多放一个数字增加贡献,但是这样就违背了数字总个数最小的法则,而且前 i − 1 i-1 i1个区间也是最小的,并不能给第 i i i个区间提供一个放数字的可能,所以我们并不能增加贡献,同时如果要增加一个贡献,那么也要让总数字加 1 1 1

当然,有可能第 i + 1 i+1 i+1个区间还和第 y y y个区间有交集,然后让前 y y y个区间总个数 + 1 +1 +1,然后贡献 + 1 +1 +1,重点是由于 r r r从小到大排,所以这个贡献是有可能作用于第 i i i个区间的,这样不就让贡献减一的同时让个数 + 1 +1 +1了吗?但是由于这个第 i i i个区间接收到了贡献,少放了一个数字,且这个数字的位置一定大于等于,第 y y y个区间增加贡献所放的数字位置,这样这个少放的数字也会作用于第 i + 1 i+1 i+1个区间,所以其实是没有增加贡献的,且由于数字前移了,对于后面的区间所能做的贡献可能会减少,是极其不优秀的。

所以第 i i i个区间同样满足此性质,证毕。在这里插入图片描述

而且不难证明,第一个区间对于第二个区间就满足这样的性质,数学归纳法完成。


#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  51000
#define  NN  110000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
inline  int  mymax(int  x,int  y){return  x>y?x:y;}
struct  node
{
	int  l,r,c,lazy,d;
}tr[NN];int  len;
void  bt(int  l,int  r)
{
	int  now=++len;tr[now].d=r-l+1;
	if(l<r)
	{
		int  mid=(l+r)>>1;
		tr[now].l=len+1;bt(l,mid);
		tr[now].r=len+1;bt(mid+1,r);
	}
}
inline  void  updata(int  x){tr[x].c=tr[tr[x].l].c+tr[tr[x].r].c;}
inline  void  get_lazy(int  x){tr[x].c=tr[x].d;tr[x].lazy=1;}
inline  void  downdata(int  x)
{
	if(tr[x].lazy)get_lazy(tr[x].l),get_lazy(tr[x].r),tr[x].lazy=0;
}
int  change(int  now,int  l,int  r,int  ll,int  rr,int  k)//返回值为改变了 
{
	int  mid=(l+r)>>1,lc=tr[now].l,rc=tr[now].r;
	downdata(now);
	if(l==ll  &&  r==rr)
	{
		int  val=0;
		if(tr[now].d-tr[now].c<=k)val=tr[now].d-tr[now].c,get_lazy(now);
		else
		{
			val=change(rc,mid+1,r,mid+1,rr,k);
			if(val<k)change(lc,l,mid,ll,mid,k-val);
			updata(now);
			val=k;
		}
		return  val;
	}
	int  val=0;
	if(rr<=mid)val=change(lc,l,mid,ll,rr,k);
	else  if(mid<ll)val=change(rc,mid+1,r,ll,rr,k);
	else
	{
		val=change(rc,mid+1,r,mid+1,rr,k);
		if(val<k)val+=change(lc,l,mid,ll,mid,k-val);
	}
	updata(now);
	return  val;
}
int  findans(int  now,int  l,int  r,int  ll,int  rr)
{
	if(l==ll  &&  r==rr)return  tr[now].c;
	downdata(now);
	int  mid=(l+r)>>1,lc=tr[now].l,rc=tr[now].r;
	if(rr<=mid)return  findans(lc,l,mid,ll,rr);
	else  if(mid<ll)return  findans(rc,mid+1,r,ll,rr);
	else  return  findans(lc,l,mid,ll,mid)+findans(rc,mid+1,r,mid+1,rr);
}
int  n;
struct  CHANGE
{
	int  x,y,z;
}ch[N];int  floor_limit=999999999,ceil_limit;
inline  bool  cmp(CHANGE  x,CHANGE  y){return  x.y<y.y;}
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)
	{
		scanf("%d%d%d",&ch[i].x,&ch[i].y,&ch[i].z);
		floor_limit=mymin(floor_limit,ch[i].x);
		ceil_limit=mymax(ceil_limit,ch[i].y);
	}
	sort(ch+1,ch+n+1,cmp);
	bt(floor_limit,ceil_limit);
	int  ans=0;
	for(int  i=1;i<=n;i++)
	{
		int  val=0;
		if((val=findans(1,floor_limit,ceil_limit,ch[i].x,ch[i].y))<ch[i].z)
		{
			ans+=change(1,floor_limit,ceil_limit,ch[i].x,ch[i].y,ch[i].z-val);
		}
	}
	printf("%d\n",ans);
	return  0;
}

但是要注意有一种贪心是错误的,即每一次选择最多集合覆盖的区域放数字,一个错误数据:

8
10 11 1
9 12 1
8 13 1
7 14 1
6 15 1
2 10 1
1 3 2
2 7 2

尤其是对于有相同的集合覆盖数的区域更加难以选择,因为选择的错误更加容易导致错误(反正这种贪心原本就是错的),比如:

4
1 2 1
2 3 1
3 4 1
4 5 1

第一个错误数据我想了好久才构造出来啊QAQ。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值