传送门


多图警告!!!

一种很新奇的\(DP\),全网似乎只有一两篇题解……

首先,序列中的一段\(e\)等价于在跳的过程中这一段\(e\)之后的一个字符必须要经过,并且在最后的答案中加上$2 \times $e的个数。

那么原题等价于:给出一个序列和两种移动方式,移动过程中必须要经过某一些点,求最小代价。

我们不妨把若干连续的\(f\)操作和若干连续的\(h\)操作看成线,那么移动路线就变成下面这样

首先,考虑下面两种移动路线

A路线一定没有B路线优,因为A路线有重复的折返。

这样说来:如果经过某些连续的\(f\)操作之后开始进行\(h\)操作,那么一定会到达要到达的最前面的目标,然后一直进行\(f\)操作不再回来。

到这里不难设计出一个暴力的\(DP\):设\(dp_{i,j}\)表示已经经过了前\(i\)个必经字符,当前光标在第\(j\)个字符时的最小代价。设字符集为\(A\),那么这种\(DP\)是\(O(N^2A)\)的,不够优秀。考虑优化。

发现上面的条件等价于对于某一个位置\(i\),经过的位置覆盖了位置\(i\)与\(i+1\)之间的线段的线的数量要么是\(1\),要么是\(3\),对应下图的\(AB\)两种情况。

到了这里就可以开始设计更加优秀的\(DP\)了

设\(p_{i,j}\)表示覆盖了\(i\)与\(i+1\)之间的线段\(1\)次,且覆盖\(i\)与\(i+1\)之间的线段的\(f\)操作选择的字符是\(j\)的最小代价,\(q_{i,j,k}\)表示覆盖了\(i\)与\(i+1\)之间的线段\(3\)次,且在进行\(h\)操作之前覆盖\(i\)与\(i+1\)之间的线段的\(f\)操作选择的字符是\(j\)、在进行\(h\)操作之后覆盖\(i\)与\(i+1\)之间的线段的\(f\)操作选择的字符是\(k\)的最小代价

又设\(s_i\)表示字符串的第\(i\)个字符,\(imp_i\)表示原串中第\(i\)个字符前是否存在字符\(e\)

转移:

\[\begin{align}p_{i,j} = & p_{i-1,j} & j \neq s_i \&\& imp_i \neq 1\\& p_{i-1,s_i} + 2 \\& q_{i-1,s_i,j} & j \neq s_i \\ & q_{i-1,s_i,s_i} + 2 \end{align}
\]

\(p_{i,j}\)的转移分别对应下图的\(ABCD\)情况

其中虚线表示新加入的线,红色字表示对应位置的字符类型,黑色字表示位置编号

\(\begin{align} q_{i,j,k} = & p_{i-1,j} + 3 & j \neq s_i \\ & p_{i-1,s_i}+5 \\ & q_{i-1,j,k} + 1 & j \neq s_i \&\& k \neq s_i \\ & q_{i-1,s_i,k} + 3 & k \neq s_i \\ & q_{i-1,j,s_i} + 3 & j \neq s_i \\ & q_{i-1,s_i,s_i} + 5 \end{align}\)

\(q_{i,j,k}\)转移分别对应下图中的\(ABCDEF\)情况

可以发现转移就是把线延长和补全的过程,所以叫做线头DP

初始值:\(f_{0,s_1}=0\),其他等于\(inf\)。最后的答案是\(f_{len,x}\),其中\(x\)是没有在字符串中出现过的字符。这可以理解成在无限远的地方有一个字符\(x\),最后一次操作就是直接跳到这一个无限远的地方。当然,这意味着最后的答案会加上跳到这个无限远的地方的\(2\)的代价,减掉\(2\)就行了。

Update:转移\(q\)的时候并不知道为什么D有用,但是不转移会WA

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
//This code is written by Itst
using namespace std; inline int read(){
int a = 0;
char c = getchar();
bool f = 0;
while(!isdigit(c) && c != EOF){
if(c == '-')
f = 1;
c = getchar();
}
if(c == EOF)
exit(0);
while(isdigit(c)){
a = a * 10 + c - 48;
c = getchar();
}
return f ? -a : a;
} const int MAXN = 7e4 + 7 , A = 11;
int f[MAXN][A] , g[MAXN][A][A] , ch[MAXN];
bool must[MAXN];
int N , M , cnt; inline char getc(){
char c = getchar();
while(!islower(c))
c = getchar();
return c;
} int main(){
#ifndef ONLINE_JUDGE
//freopen("in","r",stdin);
//freopen("out","w",stdout);
#endif
N = read();
bool ife = 1;
for(int i = 1 ; i <= N ; ++i){
char c = getc();
if(c == 'e')
cnt += (ife = 1);
else{
must[++M] = ife;
ife = 0;
ch[M] = c - 'a';
}
}
for(int i = 0 ; i < A ; ++i){
for(int j = 0 ; j < A ; ++j)
g[0][i][j] = INF;
f[0][i] = INF;
}
f[0][ch[1]] = 0;
for(int i = 1 ; i <= M ; ++i)
for(int j = 0 ; j < A ; ++j){
int t = INF;
if(j != ch[i] && !must[i])
t = min(t , f[i - 1][j]);
t = min(t , f[i - 1][ch[i]] + 2);
if(j != ch[i])
t = min(t , g[i - 1][ch[i]][j]);
t = min(t , g[i - 1][ch[i]][ch[i]] + 2);
f[i][j] = t;
for(int k = 0 ; k < A ; ++k){
t = INF;
if(j != ch[i])
t = min(t , f[i - 1][j] + 3);
t = min(t , f[i - 1][ch[i]] + 5);
if(j != ch[i] && k != ch[i])
t = min(t , g[i - 1][j][k] + 1);
if(j != ch[i])
t = min(t , g[i - 1][j][ch[i]] + 3);
if(k != ch[i])
t = min(t , g[i - 1][ch[i]][k] + 3);
t = min(t , g[i - 1][ch[i]][ch[i]] + 5);
g[i][j][k] = t;
}
}
cout << f[M][10] + 2 * cnt - 2;
return 0;
}

LOJ2687 BOI2013 Vim 线头DP的更多相关文章

  1. 【LOJ#2687】Vim(动态规划)

    [LOJ#2687]Vim(动态规划) 题面 LOJ 题解 发现移动的路径一定是每次往后跳到下一个某个字符的位置,然后往回走若干步,删掉路径上的所有\(e\),然后继续执行这个操作. 这里稍微介绍一下 ...

  2. [JZOJ3320] 【BOI2013】文本编辑器

    题目 题目大意 给你一个文本,要删去其中所有的'e'. 有三种操作: h光标左移. x删除光标上面的字母(光标是横着的). fc跳到后面的第一个字符为'c'的位置. 问操作序列的最短长度. 思考历程 ...

  3. centos7.3安装MongoDB

    安装步骤: 1.配置包管理系统 vim /etc/yum.repos.d/mongodb.repo [mongodb] name=MongoDB Repository baseurl=http://d ...

  4. 会务准备期间材料准备工作具体实施总结 ----(vim技巧应用, python信息提取与整合, microsoft word格式调整批量化)

    会务准备期间材料准备工作具体实施总结(vim, python, microsoft word) span.kw { color: #007020; font-weight: bold; } code ...

  5. vimcommandfilepatchcmdfold VIM技巧之分隔窗口 一级精华

    VIM技巧之分隔窗口 分类: 技术2010-07-08 09:57 754人阅读 评论(1) 收藏 举报   同时显示两个不同的文件, 或者同时查看同一个文件的两个不同位置, 或者是同步显示两个文件的 ...

  6. vim vi 及其相关插件的使用

    GIMP->linux下16位图查看工具 实用手册:130+ 提高开发效率的 vim 常用命令 http://www.cnblogs.com/lhb25/p/130-essential-vim- ...

  7. VIM 分割窗口

    VIM 分割窗口     *08.1*  分割窗口 打开新窗口最简单的命令如下: :split 这个命令把屏幕分解成两个窗口并把光标置于上面的窗口中: +----------------------- ...

  8. 最佳vim技巧

    最佳vim技巧----------------------------------------# 信息来源----------------------------------------www.vim ...

  9. vim多标签,多窗口

    多标签 进入vim前 vim -p <文件名> 以多标签形式打开文件.如vim -p * 就是编辑当前目录的所有文件, vim编辑中 :tabnew 增加一个标签 :tabc 关闭当前的t ...

随机推荐

  1. Windows下判断jdk是否安装好以及环境变量是否配置好

    cmd下执行: 1.java 2.javac 3.where java 如果三个都没问题,说明安装成功&环境变量配置成功

  2. easyUI combobox combotree 模糊查询,带上下键选择功能,待完善。。。。

    /2017年4月9日 11:52:36 /** * combobox和combotree模糊查询 * combotree 结果带两级父节点(手动设置数量) * 键盘上下键选择叶子节点 * 键盘回车键设 ...

  3. SSM 开发 Tars

    目录结构 tars生成的文件当成 controller 来调用 service ,service 调用 mapper POM 注意如果 mybatis是3.4.1 spring 是4.1.14的话, ...

  4. 解决git did not exit cleanly (exit code 128)

    最近在用git提交代码到部门服务器上的时候,总是有 提示 git did not exit cleanly (exit code 128).网上有2种解决方式: 1.替换路径 1.鼠标右键 -> ...

  5. PyCharm实现高效远程调试代码

      PyCharm实现高效远程调试代码   (薛刚强)    为方便Python代码学习和项目开发,目前选择专业的 IDE 开发工具 ,如 PyCham.针对个人使用的技巧做个笔记,分享给大家,有描述 ...

  6. Button's four click events

    第一种:内部类的方式 1 package com.example.phonedialer; 2 3 import com.example.click2.R; 4 5 import android.ne ...

  7. IO事件驱动模型

    1:IO事件驱动模型简介 通常,我们写服务器处理模型的程序时,有以下几种模型: (1)每收到一个请求,创建一个新的进程,来处理该请求: (2)每收到一个请求,创建一个新的线程,来处理该请求: (3)每 ...

  8. Scrapy爬取遇到的一点点问题

    学了大概一个月Scrapy,自己写了些东东,遇到很多问题,这几天心情也不大好,小媳妇人也不舒服,休假了,自己研究了很久,有些眉目了 利用scrapy 框架爬取慕课网的一些信息 步骤一:新建项目 scr ...

  9. Linux 小知识翻译 - 「分区」

    安装Linux的时候,需要对硬盘进行分区.那么「分区」到底是什么呢? 「分区」在日语中有区分,分割的意思.计算机术语中有时会说「对一个磁盘进行分区」,整个意思就是指定如何分割磁盘的意思. 「对磁盘进行 ...

  10. Appium1.9.1 之 Desired Capabilities 释疑

    服务关键字 Desired Capabilities在启动session的时候是必须提供的. Desired Capabilities本质上是以key value字典的方式存放,客户端将这些键值对发给 ...