2811: [Apio2012]Guard

2811: [Apio2012]Guard

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 673   Solved: 303
[ Submit][ Status][ Discuss]

Description

Input

Output

Sample Input

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


Sample Output

3
5

HINT

在这个样例中,有两种可能的安排方式:1,3,5 或者 2,3,5。即 3 和 5

 

后面必然躲着一个忍者。 

考虑第一个灌木丛,存在一种安排方案使得它的后面躲着忍者,但也存在一

 

种安排方案使得它后面没有躲忍者,因此不应该输出 1。同理,不应该输出 2。

Source

[ Submit][ Status][ Discuss]

码了一个线段树发现正解是贪心。。醉了,,果然我好弱啊。。好弱啊

对于某个位置,强制不放忍者,若能找到一种符合该条件的方案,使得需要忍者数小于等于k,那么这个点就不一定需要忍者(因为多出来的忍者可以随意放)
那么就变成一个有限制的寻找最优解的问题,,如果能很快回答这个最优解是多少,那么问题就解决了

对于一些根本不可能有忍者或者是输入数据中不包含的点,直接删掉
然后离散化,对应修改每个守卫看到的区间
对于任意两个新的区间,可能存在包含的情况,显然,如果小的满足那么大的一定满足,贪心地只保留小的,这样得到的最优解也还是全局最优解
这样处理剩下的区间按照左端点排序,显然满足左右端点均单调递增
对于剩下的区间中,有些点同时被多个区间包含,也就是说如果这个点放入忍者,那么就能同时满足多个区间,显然,关键点一定是这样的点
假设我们排序后从左往右处理这些区间,如果这个区间已经被前面的放置方案满足,则跳过,否则我们每次贪心地将忍者放在区间的右端点,这样得到的解显然是最优的,那么我们就定义每个区间最有价值的点为这个区间的右端点,而关键点只可能是每个区间的右端点
对于每一个剩下的区间,我们预处理从左往右到这个区间覆盖所需最少忍者数,已经最后一个忍者放在哪里,同理从右往左试一遍(这时候就是优先放左端点了),这个用贪心瞎搞一下
那么问题来了,,为什么不考虑左端点?因为关键点必定同时被多个区间包含,且如果同时有多个区间的重叠部分也是一个区间的话,这个区间的所有点都不是关键点的可能性很大,也就是说,真正的关键点趋向于刚好是两个区间只有端点相交的时候(感觉就是这样,,不能给出很严肃的证明...)于是我们处理右端点就行了

既然已经证明了一个区间的最优点是右端点,那么次优点应该就是右端点-1,二分一个k1,使得覆盖到第k1区间时,最后放置的点小于r - 1,且k1尽量大
再二分一个k2,使得从右往左覆盖到第k2个区间时,最后放置的点大于r - 1,(最后放置点是r也不影响,因为这时候这个区间长度一定大于1,也就是说我们可以当做这个点其实是放在r+1)
特判一下这时候能否解决覆盖所有区间,如果不能,则r-1这个点也必须放一个忍者,这样就是当前最优解了

写的时候注意,,,
如果删完区间剩下的长度刚好是k,可以直接全部输出
如果一个区间长度为1,这个点一定是关键点

细节很多,,,是在写的过程中慢慢总结的
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int maxn = 1E5 + 10;
const int INF = ~0U>>1;
 
int n,k,m,cnt,tot,ans,L[maxn],R[maxn],s1[maxn],Pre[maxn],s2[maxn],Min[maxn]
    ,Nex[maxn],Num[maxn],po[maxn],Max[maxn],tl[maxn],tr[maxn],Ans[maxn];
 
int getint()
{
    char ch = getchar();
    int ret = 0;
    while (ch < '0' || '9' < ch) ch = getchar();
    while ('0' <= ch && ch <= '9')
        ret = ret*10 + ch - '0',ch = getchar();
    return ret;
}
 
int main()
{
    #ifdef DMC
        freopen("DMC.txt","r",stdin);
    #endif
     
    n = getint();
    k = getint();
    m = getint();
    for (int i = 1; i <= m; i++) {
        int l,r,typ;
        l = getint();
        r = getint();
        typ = getint();
        if (!typ) ++s1[l],--s1[r+1];
        else {
            L[++cnt] = l;
            R[cnt] = r;
            ++s2[l];
            --s2[r+1];
        }
    }
     
    int rest = 0;
    for (int i = 1; i <= n; i++) {
        bool flag1,flag2;
        flag1 = flag2 = 0;
        s1[i] += s1[i-1];
        if (!s1[i]) flag1 = 1,++rest;
        s2[i] += s2[i-1];
        if (s2[i]) flag2 = 1;
        if (flag1 && flag2) {
            Num[i] = ++tot;
            po[tot] = i;
        }
    }
    if (rest == k) {
        for (int i = 1; i <= n; i++)
            if (!s1[i])
                printf("%d\n",i);
        return 0;
    }
     
    for (int i = 1; i <= n; i++)
        Pre[i] = Num[i]?i:Pre[i-1];
    for (int i = n; i; i--)
        Nex[i] = Num[i]?i:Nex[i+1];
    for (int i = 1; i <= tot; i++) Min[i] = INF;
    for (int i = 1; i <= cnt; i++) {
        L[i] = Num[Nex[L[i]]];
        R[i] = Num[Pre[R[i]]];
        Min[L[i]] = min(Min[L[i]],R[i]);
    }
    cnt = 0;
    for (int i = 1; i <= tot; i++) {
        if (Min[i] == INF) continue;
        while (cnt && R[cnt] >= Min[i]) --cnt;
        L[++cnt] = i;
        R[cnt] = Min[i];
    } 
     
    Max[0] = -1;
    for (int i = 1; i <= cnt; i++) 
        if (L[i] <= Max[i-1]) {
            tl[i] = tl[i-1];
            Max[i] = Max[i-1];
        }
        else {
            tl[i] = tl[i-1] + 1;
            Max[i] = R[i];
        }
    Min[cnt+1] = INF;
    for (int i = cnt; i; i--)
        if (R[i] >= Min[i+1]) {
            tr[i] = tr[i+1];
            Min[i] = Min[i+1];
        }
        else {
            tr[i] = tr[i+1] + 1;
            Min[i] = L[i];
        }
     
    for (int i = 1; i <= cnt; i++) {
        if (L[i] == R[i]) {
            Ans[++ans] = po[R[i]];
            continue;
        }
        int sl,sr,l,r,tmp = R[i] - 1,A,B;
        l = 0; r = cnt + 1;
        while (r - l > 1) {
            int mid = (l + r) >> 1;
            if (Max[mid] >= tmp) r = mid;
            else l = mid;
        }
        if (Max[r] < tmp) sl = tl[r],A = r;
        else sl = tl[l],A = l;
        l = 0; r = cnt + 1;
        while (r - l > 1) {
            int mid = (l + r) >> 1;
            if (Min[mid] <= tmp) l = mid;
            else r = mid;
        }
        if (Min[l] > tmp) sr = tr[l],B = l;
        else sr = tr[r],B = r;
        if (B == i) ++B;
        int Add = A >= B - 1?0:1;
        if (sl + sr + Add > k) 
            Ans[++ans] = po[R[i]];
    }
     
    if (!ans) {
        cout << -1;
        return 0;
    }
    sort(Ans + 1,Ans + ans + 1);
    for (int i = 1; i <= ans; i++)
        printf("%d\n",Ans[i]);
    return 0;
}

对于每一个剩下的区间,我们预处理从左往右到这个区间覆盖所需最少忍者数,已经最后一个忍者放在哪里,同理从右往左试一遍(这时候就是优先放左端点了),这个用贪心瞎搞一下
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值