P4766 [CERC2014]Outer space invaders

知识点:区间 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;
}
posted @ 2023-01-16 23:07  Luckyblock  阅读(47)  评论(0编辑  收藏  举报