bzoj 2811: [Apio2012]Guard(线段树+二分)

41 篇文章 0 订阅
35 篇文章 0 订阅

2811: [Apio2012]Guard

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 555   Solved: 262
[ Submit][ Status][ Discuss]

Description

Input

Output

Sample Input

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


Sample Output

3
5

HINT

在这个样例中,有两种可能的安排方式:1,3,5 或者 2,3,5。即 3 和 5

 

后面必然躲着一个忍者。 

考虑第一个灌木丛,存在一种安排方案使得它的后面躲着忍者,但也存在一

 

种安排方案使得它后面没有躲忍者,因此不应该输出 1。同理,不应该输出 2。

Source

[ Submit][ Status][ Discuss]

题解:线段树+二分

先扫描所有的区间,用线段树的区间修改将所有监视区间中没有忍者的区间标记,表示这些点中不会有忍者。然后进行线段树的点查询,将所以可能存在忍者的点重新编号,使存在忍者的区间中的编号连续。

然后用o(n)的时间扫出每个点前面第一个有编号的点(即在线段树中没有标记)和后面第一个有编号的点(如果当前点有编号,那么就是自己)

如果剩下的点数等于忍者的个数,那么直接把所有剩下的点输出(注意这里需要特判)

把所有区间按左端点排序,如果一个区间直接包含了某个区间,那么这个区间直接舍去,就可以了(因为满足了小区间就一定满足了大区间)。所以现在的左端点和右端点都是单调的。说明一个问题必须点一定出现在区间右端点,因为右端点一定能属于更多的区间(也就是右端点是最优点,在右端点放能保证忍者数最少,如果最优点都不满足条件,那么一定无解了)。那么就是需要判断区间的右端点是否必须放。首先预处理出f[i]表示前i个区间的最少要放的忍者数(从左到右没有忍者的区间在右端点放一个),g[i]表示后i个区间的最少要放的忍者数。我们就去判断当前区间的右端点x的前一个x-1这个点能否放,放x-1是需要二分一个最大的ans1,b[i].r<x-1,一个最小的ans2,使b[i].l>x-1,f[ans1]+g[ans2]+1即为当前的忍者需要量,如果大于k,说明R这个位置如果不放,就一定无法只用k个忍者来满足这些限制要求,所以R是必须的,输出其原始位置。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 200003
using namespace std;
struct data
{
	int l,r,p,mark;
};data ask[N],a[N],b[N];
int n,m,k,delta[N*4],tr[N*4],cnt,s[N],tot;
int pre[N],next[N],f[N],g[N],pos[N],st[N];
void pushdown(int x)
{
	if (!delta[x]) return;
	delta[x<<1]=delta[x<<1|1]=1;
	tr[x<<1]=tr[x<<1|1]=1;
	delta[x]=0;
}
void qjchange(int now,int l,int r,int ll,int rr,int v)
{
	if (l>=ll&&r<=rr)
	 {
	 	delta[now]=1;
		tr[now]=1; 
	 	return ;
	 }
	int mid=(l+r)/2;
	if (ll<=mid)
	 qjchange(now<<1,l,mid,ll,rr,v);
	if (rr>mid)
	 qjchange(now<<1|1,mid+1,r,ll,rr,v);
}
bool query(int now,int l,int r,int x)
{
	if (l==r) return tr[now];
	int mid=(l+r)/2;
	pushdown(now);
    if(x<=mid)
     return query(now<<1,l,mid,x);
    else
     return query(now<<1|1,mid+1,r,x);
}
void point()
{
	for (int i=1;i<=n;i++)
	  if (!query(1,1,n,i))
	   s[i]=++tot,pos[tot]=i;
}
int cmp(data a,data b)
{
	return a.l<b.l||a.l==b.l&&a.r>b.r;
}
int main()
{
	scanf("%d%d%d",&n,&k,&m);
	for (int i=1;i<=m;i++)
    {
	  scanf("%d%d%d",&ask[i].l,&ask[i].r,&ask[i].p);
	  if (!ask[i].p)
	   qjchange(1,1,n,ask[i].l,ask[i].r,1);
    }
    point();
    if (tot==k)
     {
     	for (int i=1;i<=n;i++)
     	 if (s[i])
     	  printf("%d\n",i);
     	return 0;
     }
    int t=0;
    for (int i=1;i<=n;i++)
    {
     if (s[i])  t=i;
     pre[i]=t;
    }
    t=n+1;
    for (int i=n;i>=1;i--)
    {
     if (s[i])  t=i;
     next[i]=t;
    }
    for (int i=1;i<=m;i++)
     if (ask[i].p)
      {
      	cnt++,a[cnt].l=s[next[ask[i].l]],a[cnt].r=s[pre[ask[i].r]];
      	if (a[cnt].l>a[cnt].r)
      	 cnt--;
      }
    sort(a+1,a+cnt+1,cmp);
    int top=1; st[top]=1;
    for (int i=2;i<=cnt;i++)
     {
     	while (a[i].r<=a[st[top]].r&&top)
     	 top--;
     	st[++top]=i;
     }
    for (int i=1;i<=top;i++)
     	b[i].l=a[st[i]].l,b[i].r=a[st[i]].r;
    int mx=0,mn=tot+1; 
    for (int i=1;i<=top;i++)
     if (b[i].l>mx)
      f[i]=f[i-1]+1,mx=b[i].r;
     else
      f[i]=f[i-1];
    for (int i=top;i;i--)
     if (b[i].r<mn)  g[i]=g[i+1]+1,mn=b[i].l;
     else  g[i]=g[i+1];
    f[0]=g[0]=0;
    bool flag=false;
    for (int i=1;i<=top;i++)
     {
     	if (f[i]!=f[i-1]+1) continue;
     	if (b[i].l==b[i].r)
     	 {
     	 	flag=true;
     	 	printf("%d\n",pos[b[i].l]);
     	 	continue;
     	 }
     	int x=b[i].r-1;
     	int l=1; int r=i-1;
     	int ans1=0,ans2=0;
     	while (l<=r)
     	 {
     	 	int mid=(l+r)/2;
     	 	if (b[mid].r<x) ans1=mid,l=mid+1;
     	 	else  r=mid-1;
     	 }
     	l=i+1; r=top;
     	while (l<=r)
     	 {
     	 	int mid=(l+r)/2;
     	 	if (b[mid].l>x) ans2=mid,r=mid-1;
     	 	else l=mid+1;
     	 }
     	if (f[ans1]+g[ans2]+1>k)
     	 {
     	 	flag=1;
     	 	printf("%d\n",pos[b[i].r]);
     	 }
     }
    if (!flag)
     printf("-1\n");
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值