这道题目的话,按(r)从小到大排序,然后如果对于当前的区间没有满足至少(c_i)个的话,就尽量选右边的没有选过的数字。显而易见是正确的
正确性证明:
对于当前这个区间(i)而言,如果前面(i-1)个区间已经是用了最少的数字,且尽量的把数字凑到(i)区间的(l)的的话,如果想要让前(i-1)的区间对(i)区间的贡献(+1),那么前(i-1)个区间必须多用一个数字。
那么我们证明,全部放右边能让前(i)个区间也满足这样的性质。(其实就是数学归纳法)
首先证明最少,首先前面是已经尽量的靠右边放了,也就是说对于这个区间的贡献是已经最大化了,那么由于其是最小了,所以我们不能让其更小,那么我们就只能求让其贡献变大来减少我们在这个区间放的数字个数,但是由于贡献增加数和区间少放的数字呈(1:1)关系,所以并不会让总数字减少。
那么我们继续证明贡献是最大化的情况,由于按(r)排序,所以对于第(i+1)区间交集部分而言,假设交集部分是([x,r]),如果这个区间的数字都被选了,那么就是最大化的情况,如果没有放满,说明我们可以多放一个数字增加贡献,但是这样就违背了数字总个数最小的法则,而且前(i-1)个区间也是最小的,并不能给第(i)个区间提供一个放数字的可能,所以我们并不能增加贡献,同时如果要增加一个贡献,那么也要让总数字加(1)。
当然,有可能第(i+1)个区间还和第(y)个区间有交集,然后让前(y)个区间总个数(+1),然后贡献(+1),重点是由于(r)从小到大排,所以这个贡献是有可能作用于第(i)个区间的,这样不就让贡献减一的同时让个数(+1)了吗?但是由于这个第(i)个区间接收到了贡献,少放了一个数字,且这个数字的位置一定大于等于,第(y)个区间增加贡献所放的数字位置,这样这个少放的数字也会作用于第(i+1)个区间,所以其实是没有增加贡献的,且由于数字前移了,对于后面的区间所能做的贡献可能会减少,是极其不优秀的。
所以第(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
",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。