#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();
}
as 0.4123
原文地址:https://www.cnblogs.com/Linshey/p/13873166.html