zoukankan      html  css  js  c++  java
  • [JZOJ3320] 【BOI2013】文本编辑器

    题目

    题目大意

    给你一个文本,要删去其中所有的‘e’。
    有三种操作:

    • h光标左移。
    • x删除光标上面的字母(光标是横着的)。
    • fc跳到后面的第一个字符为‘c’的位置。

    问操作序列的最短长度。


    思考历程

    首先看错了题意,然后感觉似乎很水……后来发现错了……
    接下来开始想其它的方法。
    有个还不错的思路:设(f_{i,j})表示前面(i)个‘e’被选了,现在光标在(j)的最小答案。
    比赛的时候头昏眼花写出了一个(O(n^4))的转移方程,后来在最后5分钟的时候发现其中的一对变量是重复的……也就是说,实际上是(O(n^3))……
    我就这么错过了50分……
    (后来才知道,同样是这个状态,可以优化到(O(10n^2)),具体不再赘述)


    正解

    先推荐一篇博客:https://www.cnblogs.com/Itst/p/10339605.html
    这篇博客非常详细。所以我觉得我不用说这么多了。

    这题的正解是个看起来高大上的线头DP
    什么是高大上?就是名字都没听过的东西。
    先说一开始的操作:将所有的‘e’删掉,答案预先加上(2)倍的‘e’的个数。具体原因显然。
    那么必经位置就是原先前面是‘e’的位置。
    题目转化为:从头开始,每次可以进行两种操作,问经过所有必经位置的最小答案。
    我们形象地将文本看作一个数轴,每次的操作看作走一条边,往后跳的称作飞边,往前跳的称作走边
    开始设DP状态:
    (f_{i,j})表示(i)(i+1)之间的垂线与走过的边有一个交点,显然这是和飞边的交点。(j)为飞边落下位置上的字母;
    (g_{i,j,k})表示垂线与走过的边有三个交点,显然这是和两个飞边和一个走边的交点。(j)为前面一条飞边落下位置上的字母,(k)为后面一条飞边落下位置的字母。
    可能有点不清楚,那我就借一下刚刚那片博客的图:
    在这里插入图片描述
    先考虑(f_{i,j})的转移,有以下四种情况:

    1. (f_{i-1,j})(s_i eq j)(i)不是必经点。
    2. (f_{i-1,s_i}+2)
    3. (g_{i-1,s_i,j})(s_i eq j)
    4. (g_{i-1,s_i,s_i}+2)
      在这里插入图片描述

    画画图就能理解了……再次借用图片。
    再考虑(g_{i,j})的转移,有以下六种情况(方程和别人的有很大区别,不要混淆了)。
    (nex_{i,j})表示(i)后第一个(j)的位置)

    1. (f_{i-1,j}+nex_{i,j}-i+2)(j eq s_i)在这里插入图片描述
    2. (f_{i-1,s_i}+nex_{i,j}-i+4)在这里插入图片描述
    3. (g_{i-1,j,k})(j eq s_i)(k eq s_i)在这里插入图片描述
    4. (g_{i-1,s_i,k}+nex_{i,j}-i+2)(k eq s_i)在这里插入图片描述
    5. (g_{i-1,j,s_i}+2)(j eq s_i) 在这里插入图片描述
    6. (g_{i-1,s_i,s_i}+nex_{i,j}-i+4)在这里插入图片描述

    这些图片当然也是我Copy过来的,不过要注意的是,我的转移中(i+1)(j)是已经连在一起的。
    原版的方程看别人博客去……(其实我之前一直不理解为什么他们不把(i+1)(j)连在一起,后来我终于明白,它们的状态计算的答案是(i)之前的,后面的还没有算。在后面的转移过程中会慢慢累加,补整齐。不过我觉得我这样打好理解一点
    方程完了,剩下一点细节:初始化(f_{0,s_1}=0),其它为无限大;答案加上(f_{n,'k'}),'k'为原串中没有出现过的字符。这相当于最后连一条出去(所以还要再减(2))。


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 70010
    inline void update(int &a,int b){a>b?a=b:0;}
    int _n,n;
    char _s[N],s[N];
    int nex[N][11];
    bool must[N];
    int f[N][11],g[N][11][11];
    int ans;
    int main(){
    	scanf("%d%s",&_n,_s+1);
    	ans=0;
    	for (int i=1;i<=_n;++i)
    		if (_s[i]=='e')
    			ans+=2;
    		else{
    			s[++n]=_s[i];
    			if (_s[i-1]=='e')
    				must[n]=1;
    		}
    	memset(nex[n+1],1,sizeof nex[n+1]);
    	s[n+1]='k';
    	for (int i=1;i<=n+1;++i)
    		s[i]-='a';
    	for (int i=n;i>=1;--i){
    		memcpy(nex[i],nex[i+1],sizeof nex[i]);
    		nex[i][s[i+1]]=i+1;
    	}
    	memset(f,127,sizeof f);
    	memset(g,127,sizeof g);
    	f[0][s[1]]=0;
    	for (int i=1;i<=n;++i){
    		for (int j=0;j<=10;++j){
    			if (j!=s[i]){
    				if (!must[i])
    					update(f[i][j],f[i-1][j]);
    				update(f[i][j],g[i-1][s[i]][j]);
    			}
    			update(f[i][j],f[i-1][s[i]]+2);
    			update(f[i][j],g[i-1][s[i]][s[i]]+2);
    		}
    		for (int j=0;j<=10;++j)
    			for (int k=0;k<=10;++k){
    					if (j!=s[i]){
    						update(g[i][j][k],f[i-1][j]+nex[i][j]-i+2);
    						if (k!=s[i])
    							update(g[i][j][k],g[i-1][j][k]);
    						update(g[i][j][k],g[i-1][j][s[i]]+2);
    					}
    					update(g[i][j][k],f[i-1][s[i]]+nex[i][j]-i+4);
    					if (k!=s[i])
    						update(g[i][j][k],g[i-1][s[i]][k]+nex[i][j]-i+2);
    					update(g[i][j][k],g[i-1][s[i]][s[i]]+nex[i][j]-i+4);
    				}
    	}
    	ans+=f[n][10]-2;
    	printf("%d
    ",ans);
    	return 0;
    }
    
    

    总结

    见到毒瘤题的时候要仔细找找题目的性质……
    DP时要善于分类讨论……不要被高大上的名字吓到了……

  • 相关阅读:
    对象的访问定位——如何找到对象
    对象的结构
    对象在内存中的布局-对象的创建
    java的内存模型--jmm
    redis 持久化之rdb总结
    简单说springmvc的工作原理
    抽象类和接口的区别
    hashcode和equals的作用区别及联系
    DBC物品中打包物品参数设置
    关于GOM引擎启动时显示:windows socket error: 在其上下文中,该请求的地址无效。 (10049), on API 'bind'
  • 原文地址:https://www.cnblogs.com/jz-597/p/11154338.html
Copyright © 2011-2022 走看看