[差分][二分][贪心]luogu P3634 [APIO2012]守卫

题面

https://www.luogu.com.cn/problem/P3634

给m个限制,可以是一段区间中必须有或者必须无忍者

最多有k个忍者,问有多少个位点一定有忍者

分析

首先用差分标记一下0忍者的区间,去掉

然后再删去包含了其他区间的区间,没有意义

将剩余区间按左端点排序,方便处理

考虑计算处理到第i个区间所需的最少忍者数,如果该区间内没有已经被选择的点,贪心选择右端点使总点数最少

倒着处理一遍,类似,不过是贪心选择左端点

枚举每个右端点考虑不选择它而选择它的左侧端点,并二分找到不与它相交的最近的左右两个区间

若从左侧开始处理到左侧区间的最少忍者数+从右侧开始处理到右侧区间的最少忍者数+1(选择当前区间)的所需忍者数大于k,那么说明不选这个右端点是不合法的,所以这个点必选

代码

#include <iostream> 
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e5+10;
struct Guard {
    int a,b,c;
}g[N];
int n,k,m,zm,gcnt;
int zer[N],ref[N],rcnt,nearl[N],nearr[N],f[N],h[N];
bool ans;

bool CMP(Guard a,Guard b) {return a.a<b.a;}

int main() {
    scanf("%d%d%d",&n,&k,&m);
    for (int i=1;i<=m;i++) {
        scanf("%d%d%d",&g[i].a,&g[i].b,&g[i].c);
        if (!g[i].c) zer[g[i].a]++,zer[g[i].b+1]--;
    }
    for (int i=1;i<=n;i++) {
        zer[i]+=zer[i-1];
        if (!zer[i]) ref[nearl[i]=nearr[i]=++rcnt]=i;
        else nearl[i]=n+1;
    }
    if (rcnt==k) {for (int i=1;i<=rcnt;i++) printf("%d\n",ref[i]);return 0;}
    for (int i=2;i<=n;i++) nearr[i]=max(nearr[i],nearr[i-1]),nearl[n-i+1]=min(nearl[n-i+1],nearl[n-i+2]);
    for (int i=1;i<=m;i++) if (g[i].c&&nearl[g[i].a]<=nearr[g[i].b]) g[++zm].a=nearl[g[i].a],g[zm].b=nearr[g[i].b];
    sort(g+1,g+zm+1,CMP);
    for (int i=1;i<=zm;i++) {
        while (gcnt&&g[gcnt].a<=g[i].a&&g[i].b<=g[gcnt].b) gcnt--;
        g[++gcnt]=g[i];
    }
    for (int i=1,r=0;i<=gcnt;i++) if (g[i].a>r) f[i]=f[i-1]+1,r=g[i].b; else  f[i]=f[i-1];
    for (int i=gcnt,l=n+1;i;i--) if (g[i].b<l) h[i]=h[i+1]+1,l=g[i].a; else h[i]=h[i+1];
    for (int i=1,l,r,mid,ansl,ansr;i<=gcnt;i++)
        if (f[i]!=f[i-1])
            if (g[i].a==g[i].b) printf("%d\n",ref[g[i].b]),ans=1;
            else {
                l=1;r=i-1;ansl=0;
                while (l<=r) {
                    mid=l+r>>1;
                    if (g[mid].b<g[i].b-1) l=mid+1,ansl=mid; else r=mid-1;
                }
                l=i+1;r=gcnt;ansr=n+1;
                while (l<=r) {
                    mid=l+r>>1;
                    if (g[mid].a>g[i].b-1) r=mid-1,ansr=mid; else l=mid+1;
                }
                if (f[ansl]+1+h[ansr]>k) printf("%d\n",ref[g[i].b]),ans=1;
            }
    if (!ans) printf("-1\n");
}
View Code

 

posted @ 2021-03-25 13:24  Vagari  阅读(42)  评论(0编辑  收藏  举报