[JZOJ3320] BOI2013文本编辑器

Posted jz-597

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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\\neq j\\)\\(i\\)不是必经点。
  2. \\(f_i-1,s_i+2\\)
  3. \\(g_i-1,s_i,j\\)\\(s_i\\neq 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\\neq s_i\\)技术图片
  2. \\(f_i-1,s_i+nex_i,j-i+4\\)技术图片
  3. \\(g_i-1,j,k\\)\\(j\\neq s_i\\)\\(k \\neq s_i\\)技术图片
  4. \\(g_i-1,s_i,k+nex_i,j-i+2\\)\\(k\\neq s_i\\)技术图片
  5. \\(g_i-1,j,s_i+2\\)\\(j\\neq 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\\n",ans);
    return 0;


总结

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

以上是关于[JZOJ3320] BOI2013文本编辑器的主要内容,如果未能解决你的问题,请参考以下文章

与编辑文本输入类型相关的问题

python 功能齐全的PyQt文本编辑器(http://thecodeinn.blogspot.ch/2013/07/fully-functional-pyqt-text-editor.html)移

使用 TinyMCE 作为文本编辑器

在Android中的编辑文本中从末尾键入文本[重复]

更改 eXtplorer 的文本编辑器 EditArea 的高度

如何制作自定义文本编辑器[关闭]