★☆【平衡二叉树】【倍增】会议中心

Siruseri政府建造了一座新的会议中心。许多公司对租借会议中心的会堂很感兴趣,他们希望能够在里面举行会议。 对于一个客户而言,仅当在开会时能够独自占用整个会堂,他才会租借会堂。会议中心的销售主管认为:最好的策略应该是将会堂租借给尽可能多的客户。显然,有可能存在不止一种满足要求的策略。 例如下面的例子。总共有4个公司。他们对租借会堂发出了请求,并提出了他们所需占用会堂的起止日期(如下表所示)。
        开始日期  结束日期
公司1   4         9
公司2   9         11
公司3   13        19
公司4   10        17
上例中,最多将会堂租借给两家公司。租借策略分别是租给公司1和公司3,或是公司2和公司3,也可以是公司1和公司4。注意会议中心一天最多租借给一个公司,所以公司1和公司2不能同时租借会议中心,因为他们在第九天重合了。
销售主管为了公平起见,决定按照如下的程序来确定选择何种租借策略:首先,将租借给客户数量最多的策略作为候选,将所有的公司按照他们发出请求的顺序编号。对于候选策略,将策略中的每家公司的编号按升序排列。最后,选出其中字典序最小1的候选策略作为最终的策略。 例中,会堂最终将被租借给公司1和公司3:3个候选策略是{(1,3),(2,3),(1,4)}。而在字典序中(1,3)<(1,4)<(2,3)。 你的任务是帮助销售主管确定应该将会堂租借给哪些公司。
输入格式
输入的第一行有一个整数N,表示发出租借会堂申请的公司的个数。第2到第N+1行每行有2个整数。第i+1行的整数表示第i家公司申请租借的起始和终止日期。对于每个公司的申请,起始日期为不小于1的整数,终止日期为不大于109的整数。
输出格式
输出的第一行应有一个整数M,表示最多可以租借给多少家公司。第二行应
1 字典序指在字典中排列的顺序,如果序列l1是序列l2的前缀,或者对于l1和l2的第一个不同位置j,l1[j]<l2[j],则l1比l2小。
2009 亚太地区信息学奥林匹克竞赛
会议中心(CONVENTION)
第 5 页 共 7 页
列出M个数,表示最终将会堂租借给哪些公司。
数据范围
对于50%的输入,N≤3000。在所有输入中,N≤200000。
输入样例
4
4 9
9 11
13 19
10 17
输出样例
2
1 3

在不要求字典序的情况下,不难求出一个最优解。

而要满足字典序,就必须想出其他的方法。

首先要设计出一个函数max_time(L, R),表示在区间[L, R]中最多能够满足的公司数目。

那么一个区间[L, R]在一个更大的区间[L`, R`]中可行的充要条件为:
max_time(L`, L – 1) + max_time(R + 1, R`) + 1 >= max_time(L`, R`)。(*)

于是问题的关键转化为max_time函数的计算。

再设计一个函数next(i, j)表示在区间[j, R](R为待定常数)中从左到右依次放下i个公司后的能满足条件的最小的R,若不能满足条件,则R = +∞。

用Sprase_Table的思想(即倍增思想)优化next函数,即next(i, j)表示在在区间[j, R](R为待定常数)中从左到右依次放下2^i个公司后的能满足条件的最小的R,若不能满足条件,则R = +∞。

于是max_time函数的求法就解决了。

最后,把最优性问题转化为存在性问题,从1到n枚举每个区间[L, R],在剩余的可行域中找到一个刚好包含当前被枚举的区间[L`, R`]然后用(*)式检验,若可行,则将[L`, R`]删掉并插入两个区间[L`, L – 1], [R + 1, R`]到平衡树中即可。
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <set>

using namespace std;
const char fi[] = "convention.in";
const char fo[] = "convention.out";
const int maxN = 200010;
const int MAX = 0x3f3f3f3f;
const int MIN = ~MAX;

struct Seg
{
    int L, R;
    Seg() {}
    Seg(int L, int R): L(L), R(R) {}
    bool operator<(const Seg &b) const
    {return L < b.L || L == b.L && R < b.R;}
};

set <Seg> S;
set <Seg> ::iterator iter;
Seg req[maxN], tmp[maxN], seg[maxN];
int next[20][maxN << 1];
int tab[maxN << 1];
int n, Lim, cnt, logLim;

inline void init_file()
{
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
    return;
}

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

inline bool cmp(const Seg &a, const Seg &b)
{return a.L < b.L || a.L == b.L && a.R > b.R;}

inline int plc(int x)
{
    int L = 0, R = Lim;
    while (L < R + 1)
    {
        int Mid = (L + R) >> 1;
        if (tab[Mid] == x) return Mid + 1;
        if (tab[Mid] > x) R = Mid - 1;
        else L = Mid + 1;
    }
}

inline void readdata()
{
    n = getint();
    for (int i = 0; i < n; ++i)
    {
        int L = getint(), R = getint();
        req[i] = Seg(L, R);
        tab[i << 1] = L;
        tab[(i << 1) + 1] = R;
    }
    return;
}

inline void discrete()
{
    sort(tab, tab + (n << 1));
    tab[Lim++] = tab[0];
    for (int i = 1; i < n << 1; ++i)
    if (tab[i] != tab[i - 1])
        tab[Lim++] = tab[i];
	//离散化过程。
    for (int i = 0; i < n; ++i)
        tmp[i] = req[i] = Seg(plc(req[i].L),
                              plc(req[i].R));
	//重新映射过程。
    sort(tmp, tmp + n, cmp);
    int p = n - 1;
    seg[cnt++] = tmp[n - 1];
    for (int i = n - 2; i > -1; --i)
    if (tmp[i].R < tmp[p].R)
        seg[cnt++] = tmp[p = i];
	//去掉包含其他区间的区间
	//并从按左界从大到小排序。
	//(此时又界也是排好序的。)
    return;
}

inline void next_set()
{
    int p = 0; next[0][Lim + 1] = MAX;
    for (int j = Lim; j; --j)
    if (p < cnt && j == seg[p].L)
        next[0][j] = seg[p++].R + 1;
    else next[0][j] = next[0][j + 1];
    for (int i = 1; ; ++i)
    {
        bool flag = 0;
        next[i][Lim + 1] = MAX;
        for (int k = 1; k < Lim + 1; ++k)
            flag |= (next[i][k] =
                    (next[i - 1][k] == MAX ? MAX
                    : next[i - 1][next[i - 1][k]])) < MAX;
        if (!flag) {logLim = i - 1; break;}
    }
    return;
} //建好next表。

inline int max_time(int L, int R)
{
    if (L >= R) return 0;
    int ans = 0, p = L; ++R;
    for (int j = logLim; j > -1 && p < R; --j)
    if (next[j][p] <= R) //注意这里是小于等于符号。
    {
        p = next[j][p];
        ans += 1 << j;
    }
	//数量从大到小开始放,只要能放下就累加。
    return ans;
}

inline bool judge(int i)
{
    int L = req[i].L, R = req[i].R;
    iter = S.lower_bound(Seg(L, MAX));
	//找到一个左界刚刚大于L的区间并取其前驱。
    if (iter-- == S.begin()) return 0;
	//若无前驱则返回0。
    if (iter -> R < R || iter -> L > L) return 0;
	//若找到的这个区间不能包含被枚举的区间[L, R]则返回0。
    int L1 = iter -> L, R1 = iter -> R;
    if (max_time(L1, L - 1)
        + max_time(R + 1, R1)
        + 1 < max_time(L1, R1))
        return 0;
    S.erase(iter);
    if (L1 < L) S.insert(Seg(L1, L - 1));
    if (R < R1) S.insert(Seg(R + 1, R1));
    return 1;
}

inline void work()
{
    next_set();
    printf("%d\n", max_time(1, Lim));
    S.insert(Seg(1, Lim));
	//首先把初始区域插入到平衡树中。
    for (int i = 0; i < n; ++i)
        if (judge(i)) printf("%d ", i + 1);
    printf("\n");
    return;
}

int main()
{
    init_file();
    readdata();
    discrete();
    work();
    return 0;
}

第二次做:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <set>

const char fi[] = "convention.in";
const char fo[] = "convention.out";
const int maxN = 200010;
const int MAX = 0x3f3f3f3f, MIN = ~MAX;

struct Seg
{
    int L, R;
    Seg() {}
    Seg(int L, int R): L(L), R(R) {}
    bool operator<(const Seg &b) const
    {return L < b.L || L == b.L && R < b.R;}
};
std::set <Seg> S;
std::set <Seg>::iterator iter;
Seg req[maxN], seg[maxN], tmp[maxN];
int tab[maxN << 1], next[20][maxN << 1];
int n, cnt, Lim = 1, logLim;

void init_file()
{
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
    return;
}

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

void readdata()
{
    n = getint();
    for (int i = 0; i < n; ++i)
    {
        int L = getint(), R = getint();
        req[i] = Seg(L, R);
        tab[i << 1] = L;
        tab[(i << 1) + 1] = R;
    }
    return;
}

int plc(int x)
{
    for (int L = 0, R = Lim - 1; L < R + 1;)
    {
        int Mid = (L + R) >> 1;
        if (x == tab[Mid]) return Mid + 1;
        if (x < tab[Mid]) R = Mid - 1;
        else L = Mid + 1;
    }
}

bool cmp(const Seg &a, const Seg &b)
{return a.R < b.R || a.R == b.R && a.L > b.L;}

void discrete()
{
    std::sort(tab, tab + (n << 1));
    for (int i = 1; i < n << 1; ++i)
    if (tab[i] != tab[i - 1])
        tab[Lim++] = tab[i];
    for (int i = 0; i < n; ++i)
        tmp[i] = req[i] = Seg(plc(req[i].L),
                              plc(req[i].R));
    std::sort(tmp, tmp + n, cmp);
	//这里必须要用一个临时数组,
	//保证左界右界同时单调递增。
    int p = 0; seg[cnt++] = tmp[0];
    for (int i = 1; i < n; ++i)
    if (tmp[i].L > tmp[p].L)
        seg[cnt++] = tmp[p = i];
    return;
}

void next_set()
{
    int p = cnt; next[0][Lim + 1] = MAX;
    for (int j = Lim; j; --j)
    if (p > -1 && j == seg[p - 1].L)
        next[0][j] = seg[--p].R + 1;
    else next[0][j] = next[0][j + 1];
    for (int i = 0; ; ++i)
    {
        bool flag = 0;
        next[i + 1][Lim + 1] = MAX;
        for (int k = 1; k < Lim + 1; ++k)
        {
            if (next[i][k] == MAX)
                next[i + 1][k] = MAX;
            else next[i + 1][k] = next[i][next[i][k]];
            if (next[i + 1][k] < MAX) flag = 1;
        }
        if (!flag) {logLim = i; break;}
    }
    return;
}

int max_time(int L, int R)
{
    if (L > R++) return 0;
    int ans = 0, p = L;
    for (int i = logLim; i > -1 && p < R; --i)
    if (next[i][p] <= R)
    {p = next[i][p]; ans += 1 << i;}
    return ans;
}

bool query(int i)
{
    int L = req[i].L, R = req[i].R;
    iter = S.lower_bound(Seg(L, MAX));
    if (iter-- == S.begin()) return 0;
    if (iter -> L > L || iter -> R < R)
        return 0;
    int L1 = iter -> L, R1 = iter -> R;
    if (max_time(L1, L - 1)
        + max_time(R + 1, R1)
        + 1 < max_time(L1, R1))
	//这里要满足放进去过后不影响总的答案。
        return 0;
    S.erase(iter);
    if (L1 < L) S.insert(Seg(L1, L - 1));
    if (R < R1) S.insert(Seg(R + 1, R1));
    return 1;
}

void work()
{
    printf("%d\n", max_time(1, Lim));
    S.insert(Seg(1, Lim));
    for (int i = 0; i < n; ++i)
    if (query(i))
        printf("%d ", i + 1);
    printf("\n");
    return;
}

int main()
{
    init_file();
    readdata();
    discrete();
    next_set();
    work();
    return 0;
}

    原文作者:平衡二叉树
    原文地址: https://blog.csdn.net/whjpji/article/details/7409646
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞