【问题描述】 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; }