我自闭了啊。
这么裸的、没什么知识点和技巧的题我竟然想不出来 ……
(熊猫拍桌子)
题目
分析
以下 (s_i) 和 (t_i) 分别表示活动 (i) 的开始时刻和结束时刻。时刻均离散化成 ([1,2n]) 中的整数。活动「在某个时间区间中」指活动被该区间完全包含。
我的第一想法是把活动按结束时间排序, (f_{i,j,a,b}) 表示考虑到第 (i) 个活动,当前第一个会场有 (j) 个活动,两个会场的最后一个活动分别是 (a) 和 (b) 时第二个会场的最大值。(O(1)) 决策第 (i) 个活动是放在哪个会场或者不选。单次时间复杂度 (O(n^4)) ,总时间复杂度 (O(n^5)) 。
这个通过做题的经验而不是理性分析设计出的方案是很臃肿的。考虑需要知道什么信息才能让其他事情都不影响后面的决策。如果当前选择的所有活动都在 ([1,t]) 中,那么无论这些活动如何安排,都不影响 ((t,2n]) 中的活动决策。剩下的活动都是跨过 (t) 的,不妨直接钦定 (t) 对应的状态不能选跨过 (t) 的。这样就设计出了一个新的状态:(f_{i,j}) 表示已选活动都在 ([1,i]) 中,第一个会场有 (j) 个活动时第二个会场的最大值。枚举上一个没有被跨过的位置,就有如下转移方程:
(c_{l,r}) 表示 ([l,r]) 中的活动数。这个方程的意义是决策 ((k,i]) 中的活动放在第一个还是第二个会场。显然,把这些活动全部举办不会比只举办一部分劣。这样,就在 (O(n^3)) 的时间内解决了没有限制某个活动必选时的答案。
钦定必须选某个活动,就相当于转移时不能转移到这个活动的内部(因为已经规定了不选跨过转移点的活动)。也就是说,这种限制的影响范围只局限于这个活动左右两侧最近的转移点之间。因此,再维护 (g_{i,j}) 表示已选活动都在 ([i,2n]) 中,第一个会场有 (j) 个活动时第二个会场的最大值。求法和 (f) 类似。
这样,直接枚举被钦定的活动左右两侧最近的转移点即可。因为两个会场没有区别,所以一定存在一种最优方案使得这个被钦定的活动在第一个会场:
这个单次时间复杂度是 (O(n^4)) 总复杂度 (O(n^5)) 。注意到从第三层 max 开始后面的内容就和 (s_i) 、(t_i) 无关了,因此可以预处理:
这样时间复杂度瓶颈就是算 (h) 时的 (O(n^4)) 了。
当 (i) 一定时, (f_{i,x}) 是关于 (x) 的非增函数,(g_{i,y}) 是关于 (y) 的非增函数(因为在第一个会场去掉一个活动不影响第二个会场方案的合法性,即 (f_{i,x}leq f_{i,x-1},g_{i,y}leq g_{i,y-1}))。
设
其中 (x'>x) 。那么 (F(y)) 和 (F'(y)) 是增函数,根据上面的结论可以得出 (G(y)) 和 (G'(y)) 是减函数。画个草图:
(事实上不一定是直线,但就是这个道理)
也就是说当 (x) 变大时,使 (min(F(y),G(y))) 最大的 (y) (即交点)是在变小。那么就不需要枚举 (y) ,利用单调性用指针从大往小扫就行了。时间复杂度 (O(n^3)) 。
代码
注意 (min(F(y),G(y))) 有可能有连续的一段函数值相同,所以减少 (y) 的时候只要减少后不会变差就必须减少。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
namespace zyt
{
const int N = 210, T = N << 1;
int n, m, s[N], t[N], pre[T][N], suf[T][N], ans[T][T], cnt[T][T], tmp[T];
int work()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d%d", &s[i], &t[i]), t[i] += s[i] - 1, tmp[++m] = s[i], tmp[++m] = t[i];
sort(tmp + 1, tmp + m + 1);
m = unique(tmp + 1, tmp + m + 1) - tmp - 1;
for (int i = 1; i <= n; i++)
{
s[i] = lower_bound(tmp + 1, tmp + m + 1, s[i]) - tmp;
t[i] = lower_bound(tmp + 1, tmp + m + 1, t[i]) - tmp;
}
for (int i = 1; i <= m; i++)
for (int j = i; j <= m; j++)
for (int k = 1; k <= n; k++)
if (s[k] >= i && t[k] <= j)
++cnt[i][j];
memset(pre, -1, sizeof(pre));
pre[0][0] = 0;
for (int i = 1; i <= m; i++)
for (int j = 0; j <= n; j++)
for (int k = 0; k < i; k++)
{
if (cnt[k + 1][i] <= j && ~pre[k][j - cnt[k + 1][i]])
pre[i][j] = max(pre[i][j], pre[k][j - cnt[k + 1][i]]);
if (~pre[k][j])
pre[i][j] = max(pre[i][j], pre[k][j] + cnt[k + 1][i]);
}
memset(suf, -1, sizeof(suf));
suf[m + 1][0] = 0;
for (int i = m; i > 0; i--)
for (int j = 0; j <= n; j++)
for (int k = m + 1; k > i; k--)
{
if (cnt[i][k - 1] <= j && ~suf[k][j - cnt[i][k - 1]])
suf[i][j] = max(suf[i][j], suf[k][j - cnt[i][k - 1]]);
if (~suf[k][j])
suf[i][j] = max(suf[i][j], suf[k][j] + cnt[i][k - 1]);
}
for (int i = 1; i <= m; i++)
for (int j = i; j <= m; j++)
{
int y = n;
for (int x = 0; x <= n; x++)
{
if (pre[i - 1][x] == -1)
break;
while (y > 0 && (suf[j + 1][y] == -1 ||
min(pre[i - 1][x] + suf[j + 1][y], x + y + cnt[i][j]) <=
min(pre[i - 1][x] + suf[j + 1][y - 1], x + y + cnt[i][j] - 1)))
--y;
if (~suf[j + 1][y])
ans[i][j] = max(ans[i][j], min(pre[i - 1][x] + suf[j + 1][y], x + y + cnt[i][j]));
}
}
static int res[N];
for (int i = 1; i <= n; i++)
{
for (int l = 1; l <= s[i]; l++)
for (int r = t[i]; r <= m; r++)
res[i] = max(res[i], ans[l][r]);
res[0] = max(res[0], res[i]);
}
for (int i = 0; i <= n; i++)
printf("%d
", res[i]);
return 0;
}
}
int main()
{
freopen("1973.in", "r", stdin);
return zyt::work();
}