zoukankan      html  css  js  c++  java
  • 一类利用队列优化的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
  • 相关阅读:
    docker 安装mysql
    Java web项目搭建系列之二 Jetty下运行项目
    Java web项目搭建系列之一 Eclipse中新建Maven项目
    Maven 添加其他Maven组件配置问题
    C# 中定义扩展方法
    Oracle 函数
    【Webservice】2 counts of IllegalAnnotationExceptions Two classes have the same XML type name
    Linux精简版系统安装网络配置问题解决
    Rsync 故障排查整理
    Failed to set session cookie. Maybe you are using HTTP instead of HTTPS to access phpMyAdmin.
  • 原文地址:https://www.cnblogs.com/Linshey/p/13920815.html
Copyright © 2011-2022 走看看