NKOJ 2791 (APIO 2012)守卫(贪心+链表+差分数组)

P2791【APIO2012】守卫

问题描述

APIO王国正被忍者攻击!
忍者非常厉害,因为他们在进攻的时候可以躲在阴影里面使得其他人看不到他们。整个王国除了国王居住的APIO城堡以外都已经被占领了。在城堡前,有N个灌木丛,从1到N编号,有K个忍者躲在恰好K个灌木丛后面。
APIO城堡里有M个守卫。守卫i监视着编号从Ai到Bi的连续的一段灌木丛。每个守卫都向国王报告在他所监视范围内是否有忍者出现。作为国王的仆人,你需要告诉国王,基于守卫的报告,哪些灌木丛后面一定躲着一个忍者,即对于任何和守卫报告不矛盾的忍者排列方式,在这个灌木丛后面都躲着一个忍者。
你需要写一个程序来输出所有的这些灌木丛的编号。

输入格式

第一行包含三个用空格分隔的整数N, K, M,N是灌木丛的个数,K是忍者的个数,M是守卫的个数。
接下来M行,每行描述一个守卫的信息。其中的第i行包含三个整数Ai,Bi, Ci,表示第i个守卫的监视范围是从Ai到Bi(Ai ≤ Bi)。Ci是0或者1,若是0表示范围内没有看到忍者,1表示范围内有至少一个忍者。
输入数据保证至少存在一种忍者排列方式满足所有条件。

输出格式

若存在灌木丛,在其后面一定躲着忍者,则将这些一定躲着忍者的灌木丛按照编号从小到大的顺序依次输出,每个一行。即若有X个这样的灌木丛,则需要输出X行。若不存在,则输出一行一个“-1”,不包含引号。

样例输入1:

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

样例输入2:

5 1 1
1 5 1

样例输出1:

3
5

样例输出2:

-1

提示

1 ≤ N ≤ 100,000 灌木的数量;
1 ≤ K ≤ N 忍者数;
1 ≤ M ≤ 100,000 守卫数。


这个题,显然我们需要先将没有忍者的地方标记出来,这个用差分数组实现即可。
然后剩下的位置就都可能存在忍者了。为了方便,我们把这些位置离散化一下,从而使这些位置变成编号连续的。
那么将存在忍者的区间按照左右端点排序。
之后用链表将存在忍者的区间合并一下,即如果两个区间存在包含关系,那么大的那个区间就是没用的,只需要保留小区间。
处理完了之后,所有区间的左右端点肯定都是递增的。

那么我们可以用贪心求出从左到右处理到某个区间最少要放的忍者数,因为如果要放,那么放在一个区间的右端点显然是最优的。同理可以求出从右到左到某个区间最少要放的忍者数。

之后我们考虑哪些地方一定要放,根据贪心的原则我们发现,如果一个区间的贪心值和他前一个区间一样,那么这个区间显然就不需要放,那么如果这个区间需要放,由于尽量放在右端点是最优的,那么我们假设在右端点减一的位置放一个忍者,利用前面的贪心数组,我们可以算出此时满足所有区间需要的忍者数,如果大于了总忍者数,说明放在这里是不可能的,那么就必须放在右端点,也就是说右端点必须放。于是我们只需要讨论每一个贪心时必须放的区间即可。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#define N 123456
using namespace std;
struct node{int x,y;}edge[N],E[N],tmp;
bool cmp(node a,node b)
{
    if(a.x==b.x)return a.y>b.y;
    return a.x<b.x;
}
bool cmp1(node a,node b)
{return a.x<b.x;}
bool cmp2(node a,node b)
{return a.y<b.y;}
int n,k,m,cnt,p,q,A[N],B[N],C[N],L[N],FL[N],FR[N];
bool mark[N],T;
int main()
{
    int i,j,x,y,z;
    scanf("%d%d%d",&n,&k,&m);
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        if(z==1)edge[++cnt].x=x,edge[cnt].y=y;
        else A[x]++,A[y+1]--;
    }
    for(i=1;i<=n;i++)
    {
        A[i]=A[i]+A[i-1];
        if(A[i]<=0)B[++p]=i;
    }
    if(p==k){for(i=1;i<=p;i++)printf("%d\n",B[i]);return 0;}
    for(i=1;i<=cnt;i++)
    {
        x=lower_bound(B+1,B+p+1,edge[i].x)-B;
        y=lower_bound(B+1,B+p+1,edge[i].y)-B;
        if(B[y]>edge[i].y)y--;
        edge[i].x=x;edge[i].y=y;L[i]=i-1;
    }
    sort(edge+1,edge+cnt+1,cmp);
    for(i=2;i<=cnt;i++)
    {
        while(edge[L[i]].x<=edge[i].x&&edge[L[i]].y>=edge[i].y)
        {
            mark[L[i]]=1;
            L[i]=L[L[i]];
        }
    }
    for(i=1;i<=cnt;i++)if(!mark[i])E[++q]=edge[i];
    sort(E+1,E+q+1,cmp1);
    y=0;
    for(i=1;i<=q;i++)
    if(E[i].x>y)
    {
        FL[i]=FL[i-1]+1;
        y=E[i].y;
    }
    else FL[i]=FL[i-1];
    x=n+1;
    for(i=q;i>=1;i--)
    if(E[i].y<x)
    {
        FR[i]=FR[i+1]+1;
        x=E[i].x;
    }
    else FR[i]=FR[i+1];
    for(i=1;i<=q;i++)
    {
        if(FL[i]!=FL[i-1]+1)continue;
        if(E[i].x==E[i].y)
        {
            printf("%d\n",B[E[i].x]);
            T=1;continue;
        }
        tmp.x=E[i].y;tmp.y=E[i].y-1;
        x=lower_bound(E+1,E+q+1,tmp,cmp2)-E;x--;
        y=lower_bound(E+1,E+q+1,tmp,cmp1)-E;
        if(FL[x]+FR[y]+1>k)T=1,printf("%d\n",B[E[i].y]);
    }
    if(!T)printf("-1");
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值