【题解】P1973 [NOI2011] NOI 嘉年华
yyc 学长说是典题,就记一下。
题意
给出 \(n\) 个区间,试在丢弃一些区间后,把区间分成两部分,使得不存在同时被两部分中的区间覆盖的位置,求:
-
最终包含区间数较小的部分的区间数量。
-
强制不能丢弃第 \(i\) 个区间时的答案。
注意同一部分内的区间可以重合。
\(n \leq 200, S_i, T_i \leq 10^9\)
思路
典的优化 dp.
首先考虑对区间端点进行离散化。
第一问可以考虑设计一个 dp:令 \(f[i][j]\) 表示前 \(i\) 个区间中将 \(j\) 个区间划分到第一部分时,第二部分的最大区间数量。
预处理出被 \([l, r]\) 完全包含的区间个数 \(c[l][r]\),转移可以手推:
\(\forall 0 \leq k < i, f[i][j] = \max(f[k][j - c[k + 1][i]\ ], f[k][j] + c[k + 1][i])\)
答案为 \(\max \min (f[m][i], i)\)
时间复杂度 \(O(n^3)\)
第二问实际上等价于强制将一个包含要求区间的更大区间划分到第一部分,可以枚举这个更大的区间。
不妨镜像预处理出后 \(i\) 个区间中将 \(j\) 个区间划分到第一部分时,第二部分的最大区间数量 \(g[i][j]\)。
令题意要求的区间为 \([l, r]\),不考虑强制划出的区间时,答案可以表示为:
\(\max\limits_{i = 1}^{l - 1} \max\limits_{j = r + 1}^n \max\limits_{k = 0}^n \max\limits_{t = 0}^n \min(f[i][k] + g[j][t], k + t)\)
暴力做的复杂度是假的。考虑观察一下这个式子。
对于一个划分方案,交换两部分的区间后答案显然是不变的。因此我们可以钦定第一部分的区间数量更少,那么显然将划出的区间贡献给这一部分更优。
(这里加给后面一部分理论上是可行的,但是会因为玄学边界原因锅掉)
于是答案可以表示为:
\(\max\limits_{i = 1}^{l - 1} \max\limits_{j = r + 1}^n \max\limits_{k = 0}^n \max\limits_{t = 0}^n \min(f[i][k] + g[j][t] + c[i + 1][j - 1], k + t)\)
再次观察发现枚举划出的区间时,后面的贡献是不变的,考虑先预处理出来:
\(s[i][j] = \max\limits_{k = 0}^n \max\limits_{t = 0}^n \min(f[i][k] + g[j][t] + c[i + 1][j - 1], k + t)\)
那么最后只需要 \(O(n^3)\) 的复杂度统计答案,现在的问题在于处理 \(s\).
大力模拟的复杂度显然是假的,但是它可以 AC
这个式子没有可以优化的特征,但是大力瞪眼,你会发现其中的一些项具有单调性:
\(i\) 不变时,\(f[i][k]\) 随 \(k\) 的增大而减小,\(g[j][t]\) 同理。所以当 \(k\) 增大时,\(t\) 显然单调不增。
于是大力预处理的复杂度降到了 \(O(n^3)\),可以过了。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 2e2 + 5;
const int maxt = 4e2 + 5;
const int inf = 0x3f3f3f3f;
struct act
{
int l, r;
} a[maxn];
int n, m;
int val[maxt];
int f[maxt][maxn], g[maxt][maxn];
int c[maxt][maxt], s[maxt][maxt];
int main()
{
// freopen("P1973_11.in", "r", stdin);
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &a[i].l, &a[i].r);
a[i].r += a[i].l - 1;
val[++m] = a[i].l, val[++m] = a[i].r;
}
sort(val + 1, val + m + 1);
m = unique(val + 1, val + m + 1) - val - 1;
// printf("debug %d\n", m);
for (int i = 1; i <= n; i++)
{
a[i].l = lower_bound(val + 1, val + m + 1, a[i].l) - val + 1;
a[i].r = lower_bound(val + 1, val + m + 1, a[i].r) - val + 1;
}
m += 2;
for (int i = 1; i <= m; i++)
for (int j = i; j <= m; j++)
for (int k = 1; k <= n; k++)
if ((a[k].l >= i) && (a[k].r <= j)) c[i][j]++;
for (int i = 0; i <= m + 1; i++)
for (int j = 0; j <= n; j++)
f[i][j] = g[i][j] = -inf;
f[0][0] = g[m + 1][0] = 0;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= n; j++)
{
for (int k = 0; k < i; k++)
{
if (j >= c[k + 1][i]) f[i][j] = max(f[i][j], f[k][j - c[k + 1][i]]);
f[i][j] = max(f[i][j], f[k][j] + c[k + 1][i]);
}
}
}
int ans = -inf;
for (int i = 0; i <= n; i++) ans = max(ans, min(f[m][i], i));
printf("%d\n", ans);
for (int i = m; i >= 1; i--)
{
for (int j = 0; j <= n; j++)
{
for (int k = i + 1; k <= m + 1; k++)
{
if (j >= c[i][k - 1]) g[i][j] = max(g[i][j], g[k][j - c[i][k - 1]]);
g[i][j] = max(g[i][j], g[k][j] + c[i][k - 1]);
}
}
}
for (int i = 1; i <= m; i++)
for (int j = i + 2; j <= m; j++)
{
s[i][j] = -inf;
for (int k = 0, l = n, tmp; k <= n; k++)
{
if (f[i][k] < 0) break;
while (l >= 0)
{
tmp = min(f[i][k] + g[j][l] + c[i + 1][j - 1], k + l);
if (tmp >= s[i][j]) s[i][j] = tmp;
else break;
l--;
}
l++;
}
}
for (int k = 1; k <= n; k++)
{
// printf("[%d, %d]\n", a[k].l, a[k].r);
ans = 0;
for (int i = 1; i < a[k].l; i++)
for (int j = a[k].r + 1; j <= m; j++)
ans = max(ans, s[i][j]);
printf("%d\n", ans);
}
return 0;
}