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;
}