• 一类利用队列优化的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
  • 相关阅读:
    运维:生产日志重复打印了,赶紧来看看~
    就这样,我走过了程序员的前五年。一路风雨泥泞,前方阳光正好。
    一个排序引发的BUG
    曝光一个网站,我周末就耗在上面了。
    我不服!这开源项目居然才888个星!?
    知乎的一次29.7元的咨询
    面试官:啥是请求重放呀?
    414天前,我以为这是编程玄学...
    老爷子这代码,看跪了!
    面试官一个线程池问题把我问懵逼了。
  • 原文地址:https://www.cnblogs.com/Linshey/p/13920815.html
Copyright © 2020-2023  润新知