我自闭了啊。
这么裸的、没什么知识点和技巧的题我竟然想不出来 ……
(熊猫拍桌子)
题目
分析
以下 \(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\) 个活动时第二个会场的最大值。枚举上一个没有被跨过的位置,就有如下转移方程:
\[f_{i,j}=\max_{k=0}^{i-1}\max(f_{k,j}+c_{k+1,i},f_{k,j-c_{k+1,i}}) \]
\(c_{l,r}\) 表示 \([l,r]\) 中的活动数。这个方程的意义是决策 \((k,i]\) 中的活动放在第一个还是第二个会场。显然,把这些活动全部举办不会比只举办一部分劣。这样,就在 \(O(n^3)\) 的时间内解决了没有限制某个活动必选时的答案。
钦定必须选某个活动,就相当于转移时不能转移到这个活动的内部(因为已经规定了不选跨过转移点的活动)。也就是说,这种限制的影响范围只局限于这个活动左右两侧最近的转移点之间。因此,再维护 \(g_{i,j}\) 表示已选活动都在 \([i,2n]\) 中,第一个会场有 \(j\) 个活动时第二个会场的最大值。求法和 \(f\) 类似。
这样,直接枚举被钦定的活动左右两侧最近的转移点即可。因为两个会场没有区别,所以一定存在一种最优方案使得这个被钦定的活动在第一个会场:
\[ans=\max_{l=0}^{s_i-1}\max_{r=t_j+1}^{2n}\max_{x=0}^n\max_{y=0}^n \min(x+y+c_{l+1,r-1},f_{l,x}+g_{r,y}) \]
这个单次时间复杂度是 \(O(n^4)\) 总复杂度 \(O(n^5)\) 。注意到从第三层 max 开始后面的内容就和 \(s_i\) 、\(t_i\) 无关了,因此可以预处理:
\[h_{l,r}=\max_{x=0}^n\max_{y=0}^n \min(x+y+c_{l+1,r-1},f_{l,x}+g_{r,y}) \]
\[ans=\max_{l=0}^{s_i-1}\max_{r=t_j+1}^{2n}h_{l,r} \]
这样时间复杂度瓶颈就是算 \(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}\))。
设
\[F(y)=x+y+c_{l+1,r-1} \]
\[G(y)=f_{l,x}+g_{r,y} \]
\[F'(y)=x'+y+c_{l+1,r-1} \]
\[G'(y)=f_{l,x'}+g_{r,y} \]
其中 \(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\n", res[i]);
return 0;
}
}
int main()
{
freopen("1973.in", "r", stdin);
return zyt::work();
}