【问题描述】
NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。
现在嘉年华活动的组织者小安一共收到了 n 个活动的举办申请,其中第 i 个活动的起始时间为 Si,活动的持续时间为 Ti。这些活动都可以安排到任意一个嘉年华的会场,也可以不安排。
小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。
另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相对较少的嘉年华的活动数量最大。
此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第 i 个活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华的活动数量的最大值。
【输入格式】
从文件 show.in 中读入数据。
输入的第一行包含一个整数 n,表示申请的活动个数。
接下来 n 行描述所有活动,其中第 i 行包含两个整数 Si、Ti,表示第 i 个活动从时刻 Si 开始,持续 Ti 的时间。
【输出格式】
输出到文件 show.out 中。
输出的第一行包含一个整数,表示在没有任何限制的情况下,活动较少的嘉年华的活动数的最大值。
接下来 n 行每行一个整数,其中第 i 行的整数表示在必须选择第 i 个活动的前提下,活动较少的嘉年华的活动数的最大值。
【评分标准】
对于一个测试点:
如果输出格式不正确(比如输出不足 n+1 行),得 0 分;
如果输出文件第一行不正确,而且后 n 行至少有一行不正确,得 0 分;
如果输出文件第一行正确,但后 n 行至少有一行不正确,得 4 分;
如果输出文件第一行不正确,但后 n 行均正确,得 6 分;
如果输出文件中的 n+1 行均正确,得 10 分。
【样例输入】
5
8 2
1 5
5 3
3 2
5 3
【样例输出】
2
2
1
2
2
2
【样例说明】
在没有任何限制的情况下,最优安排可以在一个嘉年华安排活动 1, 4,而在另一个嘉年华安排活动 3, 5,活动 2 不安排。
【数据规模与约定】
所有测试数据的范围和特点如下表所示
此题考察动态规划及其单调性优化。
区间离散化,设A = {嘉年华1的活动}, B = {嘉年华2的活动},C = {未安排的活动}。
设num[i][j]为包含在区间[i, j]中的区间个数;
pre[i][j]为在区间[0, i]中放入j个区间到B后,最多能放入A的个数;
suf[i][j]为在区间[i, +∞)中放入j个区间到B后,最多能放入A的个数。
那么有以下两个转移方程:
当然程序中用了向后递推的方法。
第一问就很简单地算出来了,即:
或
对于第二问,先考虑朴素做法:
设g为满足条件的最大方案。
那么:
所以最终答案为:
现在考虑优化:
对于确定的i, j,记
那么则有:随着x的增大,使得g(i, j)最大的y是逐渐减小的。
于是对于g的计算可以优化到O (n ^ 3)
Accode:
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
const int maxN = 410, INF = 0x3f3f3f3f;
int pre[maxN][maxN], suf[maxN][maxN], g[maxN][maxN];
int num[maxN][maxN], tab[maxN], L[maxN], R[maxN], n, m;
int main()
{
freopen("show.in", "r", stdin);
freopen("show.out", "w", stdout);
scanf("%d", &n);
for (int i = 0; i < n; ++i)
{
scanf("%d%d", L + i, R + i);
tab[i << 1] = L[i];
tab[(i << 1) + 1] = R[i] += L[i];
}
sort(tab, tab + (n << 1));
int cnt = unique(tab, tab + (n << 1)) - tab;
//unique函数,可以将有序的列表去重。
//这里的cnt为最终离散化过后的区间右界。
for (int i = 0; i < n; ++i)
L[i] = lower_bound(tab, tab + cnt, L[i]) - tab,
R[i] = lower_bound(tab, tab + cnt, R[i]) - tab;
//离散化过程。
for (int i = 0; i < cnt; ++i)
{
for (int j = 0; j < n; ++j)
if (L[j] >= i) ++num[i][R[j]];
for (int j = i + 1; j < cnt; ++j)
num[i][j] += num[i][j - 1];
}
memset(pre, ~0x3f, sizeof pre); pre[0][0] = 0;
memset(suf, ~0x3f, sizeof suf); suf[cnt - 1][0] = 0;
for (int i = 0; i < cnt; ++i)
{
for (int j = 0; j < n + 1; ++j)
if (pre[i][j] > ~INF)
pre[i][pre[i][j]] = max(pre[i][pre[i][j]], j);
//这里是将pre设一个初始值。
for (int j = n - 1; j > -1; --j)
pre[i][j] = max(pre[i][j], pre[i][j + 1]);
for (int j = 0; j < n + 1; ++j)
for (int k = i + 1; k < cnt; ++k)
if (pre[i][j] > ~INF)
pre[k][pre[i][j]] = max(pre[k][pre[i][j]], j + num[i][k]);
//向后递推,相当于在B中放入了pre[i][j]个区间,
//在A中放入了j + num[i][k]个区间。
}
for (int i = cnt - 1; i > -1; --i)
{
for (int j = 0; j < n + 1; ++j)
if (suf[i][j] > ~INF)
suf[i][suf[i][j]] = max(suf[i][suf[i][j]], j);
for (int j = n - 1; j > -1; --j)
suf[i][j] = max(suf[i][j], suf[i][j + 1]);
for (int j = 0; j < n + 1; ++j)
for (int k = i - 1; k > -1; --k)
if (suf[i][j] > ~INF)
suf[k][suf[i][j]] = max(suf[k][suf[i][j]], j + num[k][i]);
}
for (int i = 0; i < cnt; ++i)
for (int j = i; j < cnt; ++j)
{
g[i][j] = ~INF;
for (int x = 0, y = n; x < n + 1; ++x)
{
while (y > -1 && x + y > num[i][j] + pre[i][x] + suf[j][y]) --y;
//让x + y(即放入B的个数)刚刚小于
//num[i][j] + pre[i][x] + suf[j][y](即放入A的个数),
//这时的g值最大。
if (y > -1) g[i][j] = max(g[i][j], x + y); //
}
}
int ans = 0;
for (int i = 0; i < n + 1; ++i)
ans = max(ans, min(i, suf[0][i])); //
printf("%d\n", ans);
for (int i = 0; i < n; ++i)
{
int ans = 0;
for (int j = 0; j < L[i] + 1; ++j)
for (int k = R[i]; k < cnt; ++k)
ans = max(ans, g[j][k]);
printf("%d\n", ans);
}
return 0;
}