把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6758】Vim(DP)

  • 给定一个长度为 \(n\) 的字符串,你希望删去且仅删去其中所有的 e
  • 你可以执行三种操作:
    • x:删除光标指向的字符。
    • h:将光标左移一位。
    • fc:将光标右移到下一个字符 \(c\) 的位置(要求 \(c\) 不为 e)。
  • 初始光标在第一个字符。求最短的操作串长度。
  • \(1\le n\le7\times10^4\),字符集大小为 \(10\)

简单转化

由于移动只有左移一位和右移到某个不为 e 的字符两种,所以每一个 e 必然是由它右边的字符走过来删掉的,且一旦走到就删掉必然不劣。

则每个 e 都会固定地产生左移过来和删除这两步贡献,且在这两次操作后一定会移回到之前走过来的位置,所以我们可以转化一下:移除字符串中所有的 e,并要求原本左边是 e 的字符一定要被走到。

最优策略

考虑最优策略一定是下图这样形式的:(标出来的都是必须要走到的字符,走的过程中可以经过其他字符)

方便起见,假设若 fc 中的 \(c\) 不存在则会走到无穷远处,并强制最终走到无穷远处(只需减去最后耗费的 \(2\) 步即可得到答案),就转化成了:

然后考虑相邻两点之间的部分,要么为一次右移,要么为两次右移和一次左移。

对于必须走到的点,要么至少有一侧是两次右移和一次左移,要么在右移中停在了这个点。

动态规划+讨论

对于每一个位置,我们只需要考虑它与前一个位置之间的右移操作的种类。

所以设 \(f_{i,x}\) 表示考虑到第 \(i\) 位,第 \(i\) 位和第 \(i-1\) 位之间的操作是右移到下一个 \(x\) 的最小操作次数;\(g_{i,x,y}\) 表示考虑到第 \(i\) 位,第 \(i\) 位和第 \(i-1\) 位之间的操作是右移到下一个 \(x\)、左移、右移到下一个 \(y\) 的最小操作次数。

转移时需要一些讨论。

对于 \(f_{i,x}\),考虑以下情况:

  • \(i-1\) 位和第 \(i-2\) 位之间的操作是一次右移,且停留在第 \(i-1\) 位。转移值为 \(f_{i-1,a_{i-1}}+2\)
  • \(i-1\) 位和第 \(i-2\) 位之间的操作是一次右移,且没有停留在第 \(i-1\) 位。要求 \(a_{i-1}\not=x\),转移值为 \(f_{i-1,x}\)(注意,若第 \(i-1\) 位必须被经过,这种转移是非法的)。
  • \(i-1\) 位和第 \(i-2\) 位之间的操作是两次右移、一次左移,且后一次右移停留在第 \(i-1\) 位(前一次右移必然停留在第 \(i-1\) 位)。转移值为 \(g_{i-1,a_{i-1},a_{i-1}}+2\)
  • \(i-1\) 位和第 \(i-2\) 位之间的操作是两次右移、一次左移,且后一次右移没有停留在第 \(i-1\) 位。要求 \(a_{i-1}\not=x\),转移值为 \(g_{i-1,a_{i-1},x}\)

对于 \(g_{i,x,y}\),考虑以下情况:

  • \(i-1\) 位和第 \(i-2\) 位之间的操作是一次右移,讨论一下,转移值为 \(f_{i-1,a_{i-1}}+5\)\(f_{i-1,x}+3\)
  • \(i-1\) 位和第 \(i-2\) 位之间的操作是两次右移、一次左移,讨论一下,转移值为 \(g_{i-1,a_{i-1},a_{i-1}}+5\) \(g_{i-1,x,a_{i-1}}+3\)\(g_{i-1,a_{i-1},y}+3\)\(g_{i-1,x,y}+1\)

一点反思

模拟赛时虽然前面都想到了,但 DP 时偏了方向,死活优化不出来。

其实看到题目故意把字符集大小开小就应该往这个方向想想了?

好像也有那么一瞬想过考虑相邻位置之间的状态吧,但没想到只要记录右移类型就行了。

总之就是太菜。

代码:\(O(100n)\)

#include<bits/stdc++.h>
#define Cn const
#define CI Cn int&
#define N 70000
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
namespace FastIO
{
	#define FS 100000
	#define Tp template<typename Ty>
	#define Ts template<typename Ty,typename... Ar>
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	void reads(char* s) {int n=0;while(s[++n]=oc,!isspace(oc=tc())&&~oc);}
}using namespace FastIO;
int n,a[N+5],tg[N+5],f[N+5][11],g[N+5][11][11];char s[N+5];
int main()
{
	int i,x,y,n_=0,t=0,res=0;scanf("%d",&n);while(isspace(oc=tc()));
	for(i=1;i<=n;++i) oc=='e'?(t=1,res+=2):(a[++n_]=oc-'a',tg[n_]=t,t=0),oc=tc();n=n_;//删去所有的e
	memset(f,0x3f,sizeof f),memset(g,0x3f,sizeof g);
	for(f[1][a[1]]=0,i=2;i<=n+1;++i)
	{
		for(x=0;x<=10;++x)//转移f
		{
			f[i][x]=f[i-1][a[i-1]]+2,!tg[i-1]&&a[i-1]^x&&Gmin(f[i][x],f[i-1][x]);
			Gmin(f[i][x],g[i-1][a[i-1]][a[i-1]]+2),a[i-1]^x&&Gmin(f[i][x],g[i-1][a[i-1]][x]);
		}
		for(x=0;x<=10;++x) for(y=0;y<=10;++y)//转移g
		{
			g[i][x][y]=f[i-1][a[i-1]]+5,a[i-1]^x&&Gmin(g[i][x][y],f[i-1][x]+3);
			Gmin(g[i][x][y],g[i-1][a[i-1]][a[i-1]]+5),a[i-1]^x&&Gmin(g[i][x][y],g[i-1][x][a[i-1]]+3);
			a[i-1]^y&&Gmin(g[i][x][y],g[i-1][a[i-1]][y]+3),a[i-1]^x&&a[i-1]^y&&Gmin(g[i][x][y],g[i-1][x][y]+1);
		}
	}return printf("%d\n",f[n+1][10]+res-2),0;//加上e的贡献,并将最终答案减2
}
posted @ 2022-07-14 16:46  TheLostWeak  阅读(98)  评论(0编辑  收藏  举报