1. 程式人生 > 實用技巧 >#10470. 「2020-10-02 提高模擬賽」流水線 (line)

#10470. 「2020-10-02 提高模擬賽」流水線 (line)

題面:#10470. 「2020-10-02 提高模擬賽」流水線 (line)

題目中的那麼多區間的條件讓人感覺極其難以維護,而且貪心的做法感覺大多都能 hack 掉,因此考慮尋找一些性質,然後再設計 DP 狀態。

設兩端區間\(Q_i\)\(Q_j\)滿足\(Q_i \subseteq Q_j\),那麼顯然\(Q_j\)要麼單獨一組,要麼就和\(Q_i\)一組。

證明使用反證法,設\(Q_j\)與其他某些一組,那麼我把\(Q_j\)放入\(Q_i\)那一組,顯然兩組的答案都不會變少。

因此我們認為\(Q_j\)這一段無用了,當且僅當它單獨一組時我們再計算它的貢獻\(t_j-s_j\)。剩下的區間顯然滿足性質:左端點與右端點分別遞增,於是就可以 DP 了。設\(f_{i,j}\)

為前\(i\)個區間分成了\(j\)組後最大的收穫,狀態轉移方程:

\[f_{i,j}==\min_{k<i,t_{k+1}>s_i}{f_{k,j-1}+t_{k+1}-s_i} \]

正確性來源於這些區間的並就等於\([s_i,t_{k+1}]\),使用單調佇列優化可以實現\(\Theta(n^2)\)。最後列舉一下我選幾個之前不要的那種大區間,從大到小列舉,就可以了。

然後就不得不提這題實現的諸多細節了,因為我們要保證答案更新時一定是從合法的值更新,所以\(f\)陣列的初值統統要設為負無窮,可以避免非常多的細節,還有f[0][0]=0那一句其實是最妙的,能夠解決很多初值的問題。

memset(f, -127 / 3, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= k; i++)
{
	q[head = tail = 1] = i - 1;
	for (int j = i; j <= cnt; j++)
	{
		while (t[nw[q[head] + 1]] <= s[nw[j]] && head <= tail)
		{
			head++;
		}
		f[j][i] = f[q[head]][i - 1] + t[nw[q[head] + 1]] - s[nw[j]];
		while (head <= tail && f[j][i - 1] + t[nw[j + 1]] >= f[q[tail]][i - 1] + t[nw[q[tail] + 1]])
		{
			tail--;
		}
		q[++tail] = j;
	}
}
LL ans = 0, now = 0;
for (int i = 0; i <= k; i++)
{
	if (f[cnt][k - i])
		ans = max(ans, now + f[cnt][k - i]);
	if (hp.empty()) break;
	now += hp.top(); hp.pop();
}