一类利用队列优化的DP

I.导入:

这是一个(O(n^2))的状态和转移方程:

[f(i,j)=left{ egin{aligned} f(i-1,j-1)+k (1leq j)\ max_{k in [0,i]}{f(i-1,k)} (j=1) end{aligned} ight.]

这个方程目测是(Theta(n^2))的,但是实际上,上面的那个方程只是把数组整体位移了,下面的方程只是在位移后的数组开端添上了一个数,这个完全可以通过队列来实现,(+k)的操作,用一个整体的差分量就能实现,时间复杂度(O(n))。这个方法最伟大的一点在于,它的复杂度比状态维度还要低一维,这是让人难以想到这种方法的一大原因。

II.xj2020 画画

(n<=5000, m<=1e7).显然当两种颜色的 a 相等的时候,它们完全等价,所以我们建一个桶 (b_i) 表示长度限制为 i 的颜色个数,再设(f_{i,j,k})表示 DP 到了 i 号格子,当前颜色的长度限制是 j (其实是有点 hash 的表示颜色),当前颜色已经连续了 k 个,则状态转移方程为:

[f(i,j,k)=left{ egin{aligned} f(i-1,j,k-1) (1leq k)\ b_jsum_{j'!=j}{sum_{k'}{f(i-1,j',k')}} + (b_j-1)sum_{k'}{f(i-1,j,k')} (j=1) end{aligned} ight.]

这个状态状态转移方程上面和下面各是(Theta(n^3))的,但是我们可以发现这个转移方程,上面的可以用队列来实现整体位移,下面的可以对每个队列维护一个和,再维护一下总和,就能实现(Theta(n^2))了,主要代码如下:

for (int i = 1; i <= n; i++)
{
	q[i].push(b[i]);
	lastsum += (S[i] = b[i]);
}
for (int i = 2; i <= n; i++)
{
	for (int j = 1; j <= n; j++)
	{
		if (!b[j]) continue;
		ins[j] = ((LL)lastsum * b[j] % mod + mod - S[j]) % mod;
	}
	for (int j = 1; j <= n; j++)
	{
		if (!b[j]) continue;
		(S[j] += ins[j]) %= mod;
		(lastsum += ins[j]) %= mod;
		q[j].push(ins[j]);
	}
	for (int j = 1; j <= n; j++)
	{
		if (!b[j]) continue;
		while (q[j].size() > j)
		{
			(S[j] += mod - q[j].front()) %= mod;
			(lastsum += mod - q[j].front()) %= mod;
			q[j].pop();
		}
	}
}
fout << lastsum << Endl;

实现难度不大,主要难度在于设出状态和方程,因为这个状态有三维,很多时候我们就默认它的时间复杂度大于等于三次方,而这题偏偏又很容易得到只有两维的状态设置,很多人以为“肯定不如二维的状态设置”,但实际上,这个三维的状态可以优化到二次方,而这个二维的状态设置没法实现(Theta(1))的转移

III.xj2020 字符串

(n<=2e5,m<=20).假设 DP 到了前 i 个点,当前的两个子序列,一个肯定有一个以 i 号字符串结尾,我们只需要存另一个子序列以什么结尾;而子序列的长度都相同,说明我们只关心这个子序列最后一个串是哪一个。设(f[i][j])表示,DP 到了前 i 个点,一个子序列的结尾是 i,另一个子序列的结尾是 j 时的最短长度。综合把 i 接到 i-1 上 和把 i 接到 i-1 之前的两种情况,可以得到状态转移方程:

[f(i,j)=left{ egin{aligned} min{f(i-1,k)+cost(k,i)} (j=i-1)\ f(i-1,k)+ ext{cost}(i-1,i)(else) end{aligned} ight.]

这个状态转移方程本身已经很难想到了,需要很熟稔的分类讨论的能力。优化上,可以使用栈——上面那东西是整体加法,下面那东西是在栈底追加一个值。考虑这个 min 怎么(Theta(1))求:对于 cost 相等的 k ,显然它们是等价的。所以把已有的字符串倒序放进一个 Trie 里面,查询时正着顺着 (S_i) 走,每到一个点用(m-dep+f_X)更新答案,其中(f)是树上前缀最小值,这样会有重复情况,但是显然不影响。用整体差分就能实现 f 值的修改。

IV.「2020-09-20 五校联考」球与洞 (ballhole)

这个是国冬模拟费用流的第二题,居然被搬来了五校联考。。。

首先使用微扰法,把球和洞画成两排,把每个球连向它的洞,显然这些连线不能相交,这就及其有利于我们进行 DP。把球和洞摆成一排并排序,设(f[i][i])表示,当 (j>0)时表示剩下确定选下了 (-j)个洞待匹配的最小代价;当 (j<0)时表示 DP 到了第 (i) 个位置、剩下多少个球还待匹配的、费用提前计算的代价(提前减去绝对值函数的部分呀),那么当前位置是个球的时候,状态转移方程是:

[f(i,j)=f(i-1,j+1)+left{ egin{aligned} -pos_i (j>0)\ pos_i (else) end{aligned} ight.]

当目前位置是个洞的时候,状态转移方程就是:

[f(i,j)=min(f(i-1,j),f(i-1,j-1)+left{ egin{aligned} -pos_i (j<0)\ pos_i (else) end{aligned}) ight.]

然后把第二个式子分析一下,发现当且仅当(j=0)的时候需要取 min, 其他时候只需要直接取右手边的作为最小值即可,所以我们把 DP 数组切成三段,下标是负的一段,0一段,正的一段,就可以实现 (Theta(n))评测记录

总结:有的时候转移的复杂度比较大/状态的维度比较大的时候,我们不妨退而求其次,通过把状态升一维的代价把转移降低一维,也许就可以通过队列优化,反而能优化掉一维。

as 0.4123
原文地址:https://www.cnblogs.com/Linshey/p/13920815.html