BZOJ2811: [Apio2012]Guard

138 篇文章 0 订阅
33 篇文章 0 订阅

一开始以为是差分约束yy了个结论WA了qwq,虽然至今还不知道为什么那个结论不对

先将序列中不可能有忍者的位置标出来
如果剩下的位置恰好有K个,要特判直接全部输出

然后将每个区间缩掉两端的0
如果存在某两个区间A,B,A包含B,那么A是不必要的
处理完后剩下的区间两两互不包含,按右端点排序后,从左到右左右端点递增
易知只有每个区间的右端点有可能一定有忍者,因为贪心的放肯定是放右端点更优

用f[i]表示保证了1~i区间的限制最少需要几个忍者,g[i]表示保证了i~n的区间限制至少需要几个忍者,F[i]表示i位置一定有忍者,保证所有限制,至少需要几个忍者
f[i]和g[i]可以简单的做两个贪心得到,F[i]用两个指针找i向左第一个不能覆盖的区间,向右第一个不能覆盖的区间,O(n)扫过去得到

然后从左到右扫区间,维护一个单调队列,对于每个区间 [l,r] ,维护 [l,r) (左闭右开)内F[i]的最小值,如果这个最小值<=K,就代表右端点不是必须取的

标记,缩区间可以用并查集做到O(n)
dp,判断都可以做到O(n)
所以除去排序的复杂度是O(n)的,排序可以用桶排做到总复杂度O(n)
(然而跑的和nlogn的线段树一样快并没有什么用qwq

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;
const int maxm = 210000;

int n,m,K;
struct node { int l,r; }a[maxm]; int cnt;
inline bool cmp(const node x,const node y){return x.r==y.r?x.l>y.l:x.r<y.r;}
int fal[maxn],far[maxn],s[maxn];
int findfa(int fa[],const int x){return fa[x]==x?x:fa[x]=findfa(fa,fa[x]);}

int f[maxn],g[maxn],F[maxn];
int q[maxn],head,tail;
int ans[maxn],tp;

int main()
{
    scanf("%d%d%d",&n,&K,&m);
    for(int i=1;i<=n;i++) fal[i]=far[i]=i,s[i]=1; far[n+1]=n+1;
    for(int i=1;i<=m;i++)
    {
        int l,r,c; scanf("%d%d%d",&l,&r,&c);
        if(!c)
        {
            int j=l;
            while(j<=r)
            {
                j=findfa(far,j);
                if(j<=r) s[j]=0,far[j]=j+1;
            }
            j=r;
            while(j>=l)
            {
                j=findfa(fal,j);
                if(j>=l) s[j]=0,fal[j]=j-1;
            }
        }
        else a[++cnt]=(node){l,r};
    }
    int oth=0; for(int i=1;i<=n;i++) oth+=s[i];
    if(oth==K)
    {
        for(int i=1;i<=n;i++) if(s[i]) printf("%d\n",i);
        return 0;
    }

    for(int i=1;i<=cnt;i++)
        a[i].l=findfa(far,a[i].l),a[i].r=findfa(fal,a[i].r);
    sort(a+1,a+cnt+1,cmp);
    int temp=0;
    for(int i=1;i<=cnt;i++)
        if(a[i].l>a[temp].l) a[++temp]=a[i];
    cnt=temp;

    int r=a[1].r; f[1]=1;
    for(int i=2;i<=cnt;i++)
    {
        if(a[i].l<=r) f[i]=f[i-1];
        else f[i]=f[i-1]+1,r=a[i].r;
    }
    int l=a[cnt].l; g[cnt]=1;
    for(int i=cnt-1;i>=1;i--)
    {
        if(a[i].r>=l) g[i]=g[i+1];
        else g[i]=g[i+1]+1,l=a[i].l;
    }
    l=0,r=2; oth=1;
    for(;oth<=n;oth++)
    {
        while(a[l+1].r<oth&&l<cnt) l++;
        while(r<=cnt&&oth>=a[r].l) r++;
        F[oth]=f[l]+g[r]+1;
    }
    head=1,tail=0; r=0;
    for(int i=1;i<=cnt;i++)
    {
        while(r+1<a[i].r)
        {
            ++r;
            if(s[r])
            {
                while(head<=tail&&F[q[tail]]>=F[r]) tail--;
                q[++tail]=r;
            }
        }
        while(head<=tail&&q[head]<a[i].l) head++;
        if(head>tail||F[q[head]]>K) ans[++tp]=a[i].r;
    }
    if(!tp) puts("-1");
    //for(int i=1;i<=cnt;i++) printf("%d %d\n",a[i].l,a[i].r); puts("");
    for(int i=1;i<=tp;i++) printf("%d\n",ans[i]);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值