[APIO2009]会议中心(贪心+倍增)

[APIO2009]会议中心(贪心+倍增)

题意

有一些形如[L, R]的区间,你要选出尽可能多的区间,并满足区间两两交集为空(注意[X, X]非空) 。
求最多能选出的区间数及字典序最小的方案。

扯淡

如果不要求方案,这就是一道水题。
但是要求方案这就是一道火题辣。

题解

首先我们引入一个定理

任何不影响贪心策略的线段都存在于某一最优方法中。(这里的贪心策略指的是求最多能选出的区间数的贪心策略)

[APIO2009]会议中心(贪心+倍增)_第1张图片
如图,线段 DC,EF D C , E F 都可以选择,因为它们既不与 AB A B 相交,又不影响下一步决策(选择 GH G H

根据这个定理,这道题的做法就是按字典序选出可行的线段。
如何运用这个定理判断此线段是否能选择?

既然它满足贪心策略,那么就用贪心来检验好了。
f(l,r) f ( l , r ) 为只选择 [l,r] ∈ [ l , r ] 线段的答案。那么线段 [x,y] [ x , y ] 能选择当且仅当

f(l+1,x1)+f(y+1,r1)=f(l+1,r1) f ( l + 1 , x − 1 ) + f ( y + 1 , r − 1 ) = f ( l + 1 , r − 1 )

(l为此区间左边的已选区间的最右区间的右端点,r为此区间右边的已选区间的最左区间的左端点)
如图
[APIO2009]会议中心(贪心+倍增)_第2张图片
条件是 f(a+1,e1)+f(g+1,h1)+1=g(a+1,h1) f ( a + 1 , e − 1 ) + f ( g + 1 , h − 1 ) + 1 = g ( a + 1 , h − 1 )
证明自行脑补
贪心因为不能每次 O(n) O ( n ) 的检验所以要用倍增优化(这就是这道倍增题的唯一倍增考点???)

算法流程:

  1. 离散化线段
  2. 构建倍增数组( fi,j f i , j 的定义:从 i i 开始选择 2j 2 j 条线段,最优方案中第 j j 条线段的右端点为 fi,j f i , j
  3. 统计答案(按字典序选择,能选则选)
#include 
#include 
#include 
using namespace std;

const int maxn = 200005;
const int Inf = 1 << 30;
int n, l[maxn], r[maxn], tot, cnt, f[maxn * 2][20];
int *q[maxn * 2];
int lmax[maxn * 2], rmax[maxn * 2], lsum[maxn * 2], rsum[maxn * 2];
int ans[maxn];

inline bool cmp(const int* a, const int* b)
{
    return *a < *b;
}

int solve(int l, int r)
{
    int ans = 0;
    for(int j = 18; j >= 0; --j)
        if(f[l][j] <= r) ans += (1 << j), l = f[l][j] + 1;
    return ans;
}

#define lowbit(x) ((x) & (-x))
void add_sum(int *p, int x)
{
    while(x <= cnt) {
        ++p[x];
        x += lowbit(x);
    }
}

int query_sum(int *p, int x)
{
    int ret = 0;
    while(x > 0) {
        ret += p[x];
        x -= lowbit(x);
    }
    return ret;
}

void add_max(int *p, int x)
{
    int t = x;
    while(x <= cnt) {
        p[x] = max(p[x], t);
        x += lowbit(x);
    }
}

int query_max(int *p, int x)
{
    int ret = 0;
    while(x > 0) {
        ret = max(ret, p[x]);
        x -= lowbit(x);
    }
    return ret;
}

int main()
{
    //读入
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d%d", &l[i], &r[i]), q[++tot] = &l[i], q[++tot] = &r[i];
    //离散化
    sort(q + 1, q + tot + 1, cmp);
    for(int tmp = -1, i = 1; i <= tot; ++i) {
        if(*q[i] != tmp) {
            tmp = *q[i]; *q[i] = ++cnt;
        }else *q[i] = cnt;
    }
    //构建倍增数组
    for(int i = 1; i <= cnt + 1; ++i)
        for(int j = 0; j <= 20; ++j)
            f[i][j] = Inf;
    for(int i = 1; i <= n; ++i)
        f[l[i]][0] = min(f[l[i]][0], r[i]);
    for(int i = cnt; i >= 1; --i) {
        f[i][0] = min(f[i][0], f[i + 1][0]);
        for(int j = 1; f[i][j - 1] <= cnt && (1 << j) <= n; ++j)
            f[i][j] = f[f[i][j - 1] + 1][j - 1];
    }

    //统计答案
    tot = 0;
    for(int i = 1; i <= n; ++i) {
        int v = tot - query_sum(rsum, l[i] - 1) - query_sum(lsum, cnt - r[i]);
        if(v > 0) continue; //此区间已被覆盖
        int l_lst = query_max(rmax, l[i] - 1), r_nxt = cnt - query_max(lmax, cnt - r[i]);
        if(solve(l_lst + 1, l[i] - 1) + solve(r[i] + 1, r_nxt) + 1 == solve(l_lst + 1, r_nxt)) {
            ans[++tot] = i;
            add_sum(rsum, r[i]); add_sum(lsum, cnt - l[i] + 1);
            add_max(rmax, r[i]); add_max(lmax, cnt - l[i] + 1);
        }
    }

    //输出
    printf("%d\n", tot);
    for(int i = 1; i <= tot; ++i)
        printf("%d ", ans[i]);
    return 0;
}

你可能感兴趣的:(算法——贪心,算法——倍增)