bzoj2811 apio2012 守卫 guard 贪心

    题目意思很好理解(为什么我会想到查分约束QAQ) ,正解居然是贪心(apio和noi/noip系列确实还是赶脚不一样)

    首先要做的是把一定没有忍者的区间删除。用线段树操作比较方便,之后给每一个没被删除位置(从1---n)一个新的编号,

为了将读入的区间重新表示出来(即要使读入的L,R都应该在没被删的地方),假设区间为(L,R),如果L所处被删,自然应该去找L右边第一个没被删的,R则为左边。两次扫描就可以得到,注意不要那些已经被彻底删完了的区间。

    转回来想问题,其实就是类似于区间覆盖,会有一些灌木丛很优,能同时满足多个要求,那么大体思路是如果假设不选一个灌木丛,而其灌木丛最少需要的忍者数加起来超过了 k 的限制,那么这个被假设的灌木丛一定得选。

    用区间覆盖来解决这个问题,当把区间按左端点排序后,去掉具有包含关系的区间中大的那一个(满足了小区间就一定满足大区间),(这样所有区间左右端点都单增了)每次在一个区间的右端点放一个忍者,(这样能保证使用忍者数量最少),多余的忍者随便放即可满足,这里可以看到一个性质,就是只有区间的右端点才有可能成为必须点(理解 多余的随便放),那么现在要做的就是判断一个区间的右端点(R)是否必须放。令F[i]表示i及其之前的位置最少放多少个忍者,G[i]表示i及其之后的位置最少放多少个忍者,我们就去判断R-1这个点能否放(R-1比R-2优),放R-1是需要二分一个最大的k1,f[k1].R <R-1,一个最小的k2,使f[k2].L>R-1,F[k1]+G[k2]+1即为当前的忍者需要量,如果大于k,说明R这个位置如果不放,就一定无法只用k个忍者来满足这些限制要求,所以R是必须的,输出其原始位置。

   最后,要注意如果一开始删完区间后发现灌木丛数一共只有k个了,直接输出就好。注意-1的输出不要忘了。代码如下。

#include<iostream>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<bitset>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=400005,INF=0x7fffffff/2;
struct Data{int L,R,k;}a[N],q[N],f[N];
int n,m,k,jud[N],Ls[N],Rs[N],cnt=0,To[N],num=0,top=0;
int F[N],G[N];
void Sub(int v,int L,int R,int x,int y)
{
	int mid=(L+R)>>1;
	if(x<=L&&R<=y){jud[v]=1;return;}
	if(x<=mid)Sub(2*v,L,mid,x,y);
	if(y>mid)Sub(2*v+1,mid+1,R,x,y);
}
bool Ask(int v,int L,int R,int pos)
{
	if(jud[v])return 1;
	int mid=(L+R)>>1;
	if(L==R)return 0;
	if(pos<=mid)return Ask(2*v,L,mid,pos);
	return Ask(2*v+1,mid+1,R,pos);
}
bool cmp(const Data x,const Data y){return x.L<y.L||(x.L==y.L&&x.R>y.R);}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	int x,y,i,j;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&a[i].L,&a[i].R,&a[i].k);
		if(!a[i].k)Sub(1,1,n,a[i].L,a[i].R);
	}
	for(i=1;i<=n;i++)
	  if(!Ask(1,1,n,i))Ls[i]=Rs[i]=++cnt,To[cnt]=i;
	if(cnt==k)
	{
		for(i=1;i<=cnt;i++)printf("%d\n",To[i]);
		return 0;
	}
	Ls[0]=0;
    for(i=1;i<=n;i++)if(!Ls[i])Ls[i]=Ls[i-1];
    Rs[n+1]=INF;
    for(i=n;i>=1;i--)if(!Rs[i])Rs[i]=Rs[i+1];
	for(i=1,num=0;i<=m;i++)
	 if(a[i].k)
	 {
	 	x=Rs[a[i].L];y=Ls[a[i].R];
	 	if(x<=y)q[++num]=(Data){x,y,0};
	 }
	sort(q+1,q+num+1,cmp);
	for(i=1,top=0;i<=num;i++)
	{
		while(top>=1&&f[top].L<=q[i].L&&q[i].R<=f[top].R)top--;
		f[++top]=q[i];
	}
	int Max=0,Min=INF;
	for(i=1;i<=top;i++)
	 if(f[i].L>Max)F[i]=F[i-1]+1,Max=f[i].R;
	 else F[i]=F[i-1];
	for(i=top;i>=1;i--)
	 if(f[i].R<Min)G[i]=G[i+1]+1,Min=f[i].L;
	 else G[i]=G[i+1];
	bool SPE=0;
	int L,R,mid,k1,k2;
	for(i=1;i<=top;i++)
	{
		if(F[i]!=F[i-1]+1)continue;
		if(f[i].L==f[i].R){SPE=1;printf("%d\n",To[f[i].L]);continue;}
		x=f[i].R-1;
		k1=0;k2=top+1;
		L=1;R=i-1;
		while(L<=R)
		{
			mid=(L+R)>>1;
			if(f[mid].R<x)k1=mid,L=mid+1;
			else R=mid-1;
		}
		L=i+1;R=top;
		while(L<=R)
		{
			mid=(L+R)>>1;
			if(f[mid].L>x)k2=mid,R=mid-1;
			else L=mid+1;
		}
		if(F[k1]+G[k2]+1>k){SPE=1;printf("%d\n",To[f[i].R]);}
	}	
	if(!SPE)printf("-1");
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值