目录
知识点:区间 DP
链接:Luogu。
可能更加的阅读体验:My blog。
简述
有 \(n\) 匹黄金船赶来侵略地球。第 \(i\) 匹黄金船会在时间 \(a_i\) 出现在距离你 \(d_i\) 的位置,被消灭的时间不能大于 \(b_i\),否则你就会被外星怪马抓回她的母星每天做十万次马儿跳。在任一时刻你可以释放一次距离为 \(R\) 的忍术,花费 \(R\) 的代价将距离你不大于 \(R\) 的黄金船干掉。求不被抓走前提下干掉所有黄金船的最小代价。
\(T\) 组数据,每组数据给定长度为 \(n\) 的三个数列 \(a,b,d\),描述了一次外星怪马的侵略,求抵挡这次侵略的最小代价。
\(1\le n\le 300\),\(1\le a_i<b_i\le 10^4\),\(1\le d_i\le 10^4\)。
题面中没有给出 \(T\) 的范围,但经测试 \(T\) 并不影响总复杂度。
2S,128MB。
假算法一
我想到一个线性 DP!
显然只在恰好 \(b_i\) 时刻释放忍术是最优的。考虑先将黄金船按 \(b_i\) 升序排序,这样在后面的忍术对是否消灭了前面的黄金船没有影响。设 \(f_i\) 表示消灭前 \(i\) 匹黄金船的最小代价,转移时枚举最后一次释放忍术的时刻 \(b_j\),考虑最后一次释放忍术把 \(j\sim i\) 这一段黄金船全部消灭,则在满足 \(\left( \max_{k=j}^{i} a_i \le b_j\right)\) 条件下,有:
\[f_i = \min_{j= 1}^{i}\left( f_{j - 1}+ \max_{k = j}^{i} d_k\right) \]答案即 \(f_n\),总复杂度 \(O(n)\) 级别。
然而假了。虽然排序后后面的忍术不会影响前面的黄金船,但前面的忍术会影响到后面的黄金船。具有后效性,无法进行线性 DP。
真算法一
发现 \(n\) 较小,但 \(a,b\) 较大,考虑先离散化,记离散化后最大的时刻为 \(m\)。
按时间顺序枚举具有后效性,则考虑对时间维进行区间 DP。设 \(f_{i,j}\) 表示将满足 \(i\le a_p < b_p\le j\) 的黄金船 \(p\) 全部消灭的代价。转移时考虑最后在位置 \(k(i\le k\le j)\) 进行的一次操作,在这次操作之前消灭了 \([a_p,b_p]\subseteq [i,k-1]\) 和 \([a_p, b_p]\subseteq [k+1,i]\) 中的所有黄金船 \(p\),这次操作消灭了满足 \(i\le a_p \le k \le b_p\le j\) 的全部黄金船,即有:
\[f_{i,j} = \min_{k=i}^{j}\left( f_{i, k - 1} + f_{k + 1, j} + d_{i,j,k}\right) \]上式中 \(d_{i,j,k}\) 表示满足:\(i\le a_p \le k \le b_p\le j\) 的黄金船 \(p\) 中最远的距离,即:
\[d_{i,j,k} = \max_{i\le a_p \le k \le b_p\le j} d_p \]最终答案即 \(f_{1,m}\)。
发现 DP 的过程是 \(O(n^3)\) 级别的,但预处理 \(d\) 是 \(O(n^4)\) 级别的,不预处理则 DP 变为 \(O(n^4)\) 级别,无法通过本题。
真算法二
发现复杂度瓶颈出在取最大值的过程中。取最大值的目的是得到 \([a_p, b_p]\) 跨越区间分界点 \(k\) 的最大的 \(d_p\),计算出使得 \([i,j]\) 中的黄金船全部被消灭的最后一次操作的代价。
发现操作的顺序并不影响正确性,我们不妨钦定满足 \([a_p,b_p]\subseteq [i,j]\) 的 \(d_p\) 最大的黄金船是在最后一次操作中被消灭的。转移时先求得这匹黄金船的编号 \(\operatorname{id}\),则枚举的分界点 \(k\) 被限定在了 \([a_{\operatorname{id}}, b_{\operatorname{id}}]\) 中,有:
\[f_{i,j} = \min_{k=a_{\operatorname{id}}}^{b_{\operatorname{id}}}\left( f_{i, k - 1} + f_{k + 1, j} + d_{\operatorname{id}}\right) \]仅需在枚举 \(k\) 之前 \(O(n)\) 地求得 \(\operatorname{id}\) 即可。答案仍为 \(f_{1,m}\)。总复杂度 \(O(n^3)\) 级别,可以通过本题。
真算法三
真算法二和其他题解中的写法已经非常类似了,但其他题解中在转移时没有分界点 \(k\) 的限制,为什么仍然得到了正确的答案呢?换言之,怎么保证 \(k\notin [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移一定不优于 \(k\in [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移呢?
需要指出的是,真算法二实际上是对真算法一的一种减去无用转移的优化。我们考虑一次 \(k\notin [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移 \(f_{i,j} = \min\left( f_{i, k - 1} + f_{k + 1, j} + d_{\operatorname{id}}\right)\)。此时有 \([a_{\operatorname{id}}, b_{\operatorname{id}}] \subseteq [i,k-1]\) 或 \([a_{\operatorname{id}}, b_{\operatorname{id}}]\subseteq [k+1, j]\),若这里的 \(f_{i,j}\) 定义不变,则最大的代价 \(d_{\operatorname{id}}\) 已经在 \(f_{i,k-1}\) 或 \(f_{k+1,j}\) 中被计算了一次了。更进一步地,在真算法一中,我们在计算 \(f_{i,j}\) 时选择的是 \(k\in [a_p, b_p]\) 的最大的代价 \(d_p\),显然有 \(d_p\le d_{\operatorname{id}}\)。当我们在真算法二中进行 \(k\in [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移时,\(d_p\) 的贡献也是已经被包含在了 \(f_{i,k-1}\) 或 \(f_{k+1,j}\) 中了。
总结一下,\(k\notin [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移实质上都是 \(k\in [a_{\operatorname{id}}, b_{\operatorname{id}}]\) 的转移重复状态,把它们都考虑上也并不会影响正确性。
代码
真算法二
//By:Luckyblock
/*
知识点:区间 DP
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 300 + 10;
const int kD = kN << 1;
const int Inf = 1e9;
//=============================================================
int n, l[kN], r[kN], d[kN], f[kD][kD];
int dnum, data[kD];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
void Init() {
n = read();
for (int i = 1; i <= n; ++ i) {
l[i] = read(), r[i] = read(), d[i] = read();
data[i] = l[i], data[i + n] = r[i];
}
dnum = 0;
std::sort(data + 1, data + 2 * n + 1);
for (int i = 1; i <= 2 * n; ++ i) {
if (data[i] != data[i - 1]) {
data[++ dnum] = data[i];
}
}
for (int i = 1; i <= n; ++ i) {
l[i] = std::lower_bound(data + 1, data + dnum + 1, l[i]) - data;
r[i] = std::lower_bound(data + 1, data + dnum + 1, r[i]) - data;
}
}
void DP() {
for (int i = 1; i <= dnum; ++ i) {
for (int j = 1; j <= dnum; ++ j) {
f[i][j] = Inf;
}
}
for (int lth = 1; lth <= dnum; ++ lth) {
for (int ll = 1, rr = ll + lth - 1; rr <= dnum; ++ ll, ++ rr) {
int maxd = 0, maxdid = 0;
for (int k = 1; k <= n; ++ k) {
if (ll <= l[k] && r[k] <= rr) {
if (d[k] > maxd) maxd = d[k], maxdid = k;
}
}
if (!maxd) {
f[ll][rr] = 0;
continue;
}
for (int k = l[maxdid]; k <= r[maxdid]; ++ k) {
f[ll][rr] = std::min(f[ll][rr],
f[ll][k - 1] + f[k + 1][rr] + maxd);
}
}
}
}
//=============================================================
int main() {
int T = read();
while (T --) {
Init();
DP();
printf("%d\n", f[1][dnum]);
}
return 0;
}
标签:le,Outer,忍术,算法,operatorname,invaders,CERC2014,id,黄金
From: https://www.cnblogs.com/luckyblock/p/17056671.html