[BZOJ2811][Apio2012]Guard(线段树+贪心+二分)

70 篇文章 0 订阅
57 篇文章 0 订阅

题目描述

传送门

题解

首先用线段树来判断一下某一个点是否一定没有忍者。
然后加一个特判,如果一定没有忍者的点的数量已经等于n-k的话,剩下的点一定都有忍者,直接输出。
因为有一些点是一定没有忍者的,可以将这些点在整个序列上扣掉,然后将其他有意义的点重新编号。
重新编号之后原来合法的区间的左右端点有可能是不合法的,那么就要将区间缩一下,方法是处理pre和next分别表示当前点前面和后面第一个可能有忍者的点,然后对于每一个区间,l变成next->l,r变成pre->r
如果一个区间被另一个区间覆盖,那么那个大的区间可以扔掉,因为如果这两个区间都要满足至少有一个,那么满足了小的就能满足大的。这样扔掉一些区间之后按照左端点排序就得到了左端点单调不降,右端点也单调不降的区间。
然后又得到了一些新的合法的区间,有一个结论是只有每一个区间的右端点才有可能是必须点,因为相对于这个区间的其他点来说这个点一定是最优的。那么我们每次贪心地将忍者放在区间的右端点,如果这个区间已经被以前的区间覆盖了那就不用了。我们现在只需要判断如果将这个点移到区间的右端点-1(次优点)是否能满足要求就行了,如果能满足,则这个点不是必须点,否则是必须点。
方法就是预处理f和g数组,分别表示第i个区间之前和之后最少放几个忍者,这个预处理可以用贪心来做。
然后对于每一个要判断的最优点R,找到最大的k1使interval[k1].r < <script type="math/tex" id="MathJax-Element-1"><</script>R-1,找到最小的k2使interval[k2].l > <script type="math/tex" id="MathJax-Element-2">></script>R-1,然后f[k1]+g[k2]+1就是需要放的忍者,如果大于k的话,说明这个解不合法,那么R这个点一定为必须点,否则不是必须点。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

const int max_n=1e5+5;
const int max_m=1e5+5;
const int max_tree=max_n*4;
const int inf=2e9;

int n,m,k,x,y,ty,cnt,A,B,tot,now,l,r,mid,k1,k2,number;
int point[max_n],pre[max_n],next[max_n],num[max_n],f[max_m],g[max_m],hash[max_n];
int sum[max_tree],delta[max_tree];
struct hp{int x,y;}a[max_m],b[max_m],interval[max_m];

inline void update(int now){sum[now]=sum[now<<1]+sum[now<<1|1];}
inline void pushdown(int now,int l,int r,int mid){
    if (delta[now]){
        sum[now<<1]=mid-l+1; delta[now<<1]=1;
        sum[now<<1|1]=r-mid; delta[now<<1|1]=1;
        delta[now]=0;
    }
}
inline void interval_change(int now,int l,int r,int lrange,int rrange){
    int mid=(l+r)>>1;
    if (lrange<=l&&r<=rrange){
        sum[now]=r-l+1;
        delta[now]=1;
        return;
    }
    if (lrange<=mid) interval_change(now<<1,l,mid,lrange,rrange);
    if (mid+1<=rrange) interval_change(now<<1|1,mid+1,r,lrange,rrange);
    update(now);
}
inline int query(int now,int l,int r,int x){
    int mid=(l+r)>>1;
    if (l==r) return sum[now];
    pushdown(now,l,r,mid);
    if (x<=mid) return query(now<<1,l,mid,x);
    else return query(now<<1|1,mid+1,r,x);
}
inline int cmp(hp a,hp b){return a.x<b.x||(a.x==b.x&&a.y<b.y);}
int main(){
    scanf("%d%d%d",&n,&k,&m);
    for (int i=1;i<=m;++i){
        scanf("%d%d%d",&x,&y,&ty);
        if (ty==0) interval_change(1,1,n,x,y);
        else a[++A].x=x,a[A].y=y;
    }
    for (int i=1;i<=n;++i){
        point[i]=query(1,1,n,i)^1;
        if (point[i]) num[i]=++cnt,hash[cnt]=i;
    }
    if (cnt==k){
        for (int i=1;i<=n;++i)
          if (num[i]) printf("%d\n",i);
        return 0;
    }

    for (int i=1;i<=n;++i) if (!point[i]) pre[i]=pre[i-1]; else pre[i]=i;
    for (int i=n;i>=1;--i) if (!point[i]) next[i]=next[i+1]; else next[i]=i;
    for (int i=1;i<=A;++i){
        a[i].x=next[a[i].x]; a[i].y=pre[a[i].y];
        if (a[i].x<=0||a[i].x>n||a[i].y<=0||a[i].y>n||a[i].x>a[i].y) continue;
        b[++B].x=num[a[i].x]; b[B].y=num[a[i].y];
    }

    sort(b+1,b+B+1,cmp);
    for (int i=B,j;i>=1;i=j){
        j=i-1;
        while (b[j].y>=b[i].y) j--;
        interval[++tot].x=b[i].x,interval[tot].y=b[i].y;
    }
    sort(interval+1,interval+tot+1,cmp);

    int mx=0,mn=inf;
    for(int i=1;i<=tot;i++)
        if(interval[i].x>mx)f[i]=f[i-1]+1,mx=interval[i].y;
        else f[i]=f[i-1];
    for(int i=tot;i;i--)
        if(interval[i].y<mn)g[i]=g[i+1]+1,mn=interval[i].x;
        else g[i]=g[i+1];

    bool flag=false;
    int i=2; now=1;
    while (i<=tot+1){
        if (interval[now].y-1<interval[now].x){
            flag=true;
            printf("%d\n",hash[interval[now].y]);
            while (i<=tot&&interval[i].x<=interval[now].y) i++;
            now=i;
            i++;
            continue;
        }
        l=1; r=now-1; mid=0; k1=0;
        while (l<=r){
            mid=(l+r)>>1;
            if (interval[mid].y<interval[now].y-1) k1=mid,l=mid+1;
            else r=mid-1;
        }
        l=now+1; r=tot; mid=0; k2=0;
        while (l<=r){
            mid=(l+r)>>1;
            if (interval[mid].x>interval[now].y-1) k2=mid,r=mid-1;
            else l=mid+1;
        }
        if (f[k1]+g[k2]+1>k) flag=true,printf("%d\n",hash[interval[now].y]);

        while (i<=tot&&interval[i].x<=interval[now].y) i++;
        now=i;
        i++;
    }
    if (!flag) printf("-1\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值