bzoj2811: [Apio2012]Guard(二分+差分)

Problem

n n 个灌木丛,其中有 k 个后面有士兵
m m 个区间,每个区间至少有 1 名士兵 ( op=1 o p = 1 ),或者一个士兵都没有 (op=0) ( o p = 0 )
问哪个灌木丛后面一定有士兵

Solution

首先,我们可以把是 0 0 的区间删去,然后对序列进行重标号
做法:差分/线段树

重标号后,n 发生变换
此时这 n n 个点都是可以放士兵的
那么我们可以特判掉 n=k 的情况

然后我们发现如果又两个区间 (l1,r1),(l2,r2) ( l 1 , r 1 ) , ( l 2 , r 2 ) ,且 l1<=l2<=r2<=r1 l 1 <= l 2 <= r 2 <= r 1 (l1,r1) ( l 1 , r 1 ) 中除去 (l2,r2) ( l 2 , r 2 ) 部分,都是可有可无的。那我们就删去第一个区间,只保留第二个
做法:把所有区间按 l l 从大到小排序,那么此时对于每个一点 i,那么他之前的点 j j 一定会有 li<=lj,那么如果存在 rj<=ri r j <= r i ,则就有 li<=lj<=rj<=ri l i <= l j <= r j <= r i ,那么 (li,ri) ( l i , r i ) 就可以删去了。
(脑残的用了set….)

此时,所有的 lr l 、 r 分别都是递增的了(这个一会会用到)
然后我们预处理出 f[i]g[i] f [ i ] 、 g [ i ] 分别表示从前到后、从后到前最少放多少兵能满足前/后 i i 个条件
做法(对于从前到后,从后到前反过来就行):按照左端点排序,记录前一个放士兵放在哪里,如果这个士兵正好在这个区间内,那么说明这个区间不再需要其他的点,而如果不在,那我们就将这个区间的右端点放上士兵(这个一定是最优的,对于后面的来说)。此时我们还要记录这个区间的右端点需不需要放上士兵(同后面需要用)。

题目中询问的是有哪个点必须放上士兵,根据之前求出的 f[i],这些士兵出自区间右端点,然而不一定是必须的。那我们考虑对于每一个区间,如果右端点前一个点放上士兵同样能满足 k k 这个条件,那么说明右端点不是必须的放上士兵;反之,必须此右端点必须放上士兵。那我们就是去找前一个点所在的区间 (l,r),那么此时我们需要的士兵数就是 f[l1]+g[r+1]+1 f [ l − 1 ] + g [ r + 1 ] + 1 ,若此数 <=k <= k <script type="math/tex" id="MathJax-Element-36"><=k</script> 则说明在右端点前一个点放上士兵也可以满足条件,此右端点不符合题目要求;反之输出即可
对于寻找 x x 所在的区间 (l,r) ,我用的是二分。但是吧…其实…因为 x= x = 右端点 1 − 1 ,那么右端点是递增的,右端点减一也是递增的, 所有直接推着走就行了

良心的几点注意(wa的猝不及防):

  1. 要记得还要输出 1 − 1
  2. 对于最后的部分,要考虑此区间右端点是否被放置了士兵,若没放置,则不存字寻找包含一说
  3. 要注意有的区间可能没有右端点减一这个点,即区间只有一个数,那么就直接输出好了

Code

#include <cstdio>
#include <set>
#include <algorithm>
using namespace std;
#define N 100010
set<int>S;
int n,m,k,cnt=0,sz=0,b[N],mx[N],mn[N],f[N],g[N],val[N],isr[N];
struct node{int x,y,op;}a[N],c[N]; 
inline bool cmp1(node x,node y){return x.x>y.x || (x.x==y.x && x.y<y.y);}
inline bool cmp2(node x,node y){return x.x<y.x;}
inline bool cmp3(node x,node y){return x.x>y.x;}
int main(){
    freopen("in.txt","r",stdin);
    freopen("a.in","w",stdout); 
    scanf("%d%d%d",&n,&k,&m);int m1=0;
    for(int i=1;i<=m;i++){
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        if(!z) b[x]++,b[y+1]--;
        else a[++m1].x=x,a[m1].y=y;
    }m=m1;
    //重标号 
    for(int i=1;i<=n;i++) b[i]+=b[i-1];
    int now=0;
    for(int i=1;i<=n;i++){
        if(b[i]<=0) val[++now]=i;
    }
    now=0;
    for(int i=1;i<=n;i++){
        if(!b[i]) now++;
        mn[i]=now;
    }
    now++;
    for(int i=n;i>=1;i--){
        if(!b[i]) now--;
        mx[i]=now;
    }
    for(int i=1;i<=m;i++){
        a[i].x=mx[a[i].x];
        a[i].y=mn[a[i].y];
    }
    n=mn[n];

    //特判 
    if(n==k){
        for(int i=1;i<=n;i++) printf("%d\n",val[i]);
        return 0;
    }

    //删去包含其他线段的线段 
    sort(a+1,a+m+1,cmp1);
    for(int i=1;i<=m;i++){
        if(i!=1 && a[i].y>= *S.begin()) a[i].op=1;
        else S.insert(a[i].y);
    }
    m1=0;
    for(int i=1;i<=m;i++) if(!a[i].op){
        c[++m1]=a[i];
    }
    m=m1;
    for(int i=1;i<=m;i++) a[i]=c[i];

    //求 f、g 数组 
    sort(a+1,a+m+1,cmp2); 
    int pre=0;
    for(int i=1;i<=m;i++){
        f[i]=f[i-1]; 
        if(a[i].x>pre) f[i]++,isr[i]=1,pre=a[i].y;
    }
    pre=0x3f3f3f3f;
    for(int i=m;i>=1;i--){
        g[i]=g[i+1];
        if(a[i].y<pre) g[i]++,pre=a[i].x;
    }

    //二分查找区间,判断结果 
    for(int i=1;i<=m;i++){
        if(!isr[i]) continue;
        int l=1,r=i-1,ans1=i;
        if(a[i].x==a[i].y){printf("%d\n",val[a[i].x]),sz++;continue;}
        while(l<=r){
            int mid=l+r>>1;
            if(a[mid].x<=a[i].y-1 && a[i].y-1<=a[mid].y) ans1=mid,r=mid-1;
            else l=mid+1;
        }
        l=i+1;r=m;int ans2=i; 
        while(l<=r){
            int mid=l+r>>1;
            if(a[mid].x<=a[i].y-1 && a[i].y-1<=a[mid].y) ans2=mid,l=mid+1;
            else r=mid-1;
        }
        if(f[ans1-1]+g[ans2+1]+1>k) printf("%d\n",val[a[i].y]),sz++;
    }
    if(!sz) puts("-1");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值