传送门


多图警告!!!

一种很新奇的\(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. Spring MVC异常处理 和 重定向传递数据

    1.异常处理介绍 Spring在web项目中,如果在请求处理时出现异常,那输出会是Servlet响应.这时异常需要以某种方式转换为响应. Spring将异常转换为响应的方式: a.特定的Spring异 ...

  2. MSCRM中报表开发二:创建基于FetchXML报表

    1. 获取FetchXML.因为FetchXML难以撰写,所以我们一般都是使用高级查找来生成FetchXML或者通过其他工具来生成.我这里在商机界面通过高级查找制作了一个新的视图,名称为 商机查询,  ...

  3. datagridview 行高列宽的自动设置

    1) 设定行高和列宽自动调整 [C#]// 设定包括Header和所有单元格的列宽自动调整 DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSi ...

  4. Java——语句

    空语句: ; 符合语句:{} if语句:if () {} else {} break 语句:跳出单层循环 循环语句:for(;;){}   do{}while()

  5. Excel实用录入技巧

    一.文本录入技巧 输入开头为0的序号 当直接输入单元格中的数字第一个为0时系统会默认去掉 只需要经单元格格式改为文本或者在单元格输入前使用英文状态下的单引号(‘) 例如:'0001 >>& ...

  6. java 按字节读写二进制文件(Base64编码解码)

    最近在做项目时遇到这样一个需求:依次读取本地文件夹里所有文件的内容,转为JSON,发送到ActiveMQ的消息队列, 然后从MQ的消息队列上获取文件的信息,依次写到本地.常见的文件类型,比如.txt ...

  7. 彻底卸载删除Win10易升,禁止再生

    易升是微软推出的win10升级工具.用户可通过易升一键升级win10. 因为我的电脑已经是win10的系统,所以我也不需要升级.也不想升级,因为我从网上了解到升级后的系统反而没有升级前的好用. 微软的 ...

  8. Django框架的使用教程--视图和路由[二]

    视图和路由 1.创建一个django_test应用 2.setting中设置django_test INSTALLED_APPS = [ 'django.contrib.admin', 'django ...

  9. zabbix使用自定义脚本监控内存

    我这里的脚本是监控centos7系统的内存.centos7系统的内存如何查看我之前的博客都是有的.这里直接写了监控步骤 1.首先是编写脚本. #!/bin/bash mem_total(){ TOTA ...

  10. PHP实现一个简陋的注册登录页面

    PHP实现一个简陋的注册登录页面 今天来水一篇没有**用的 /滑稽脸,代码简陋臃肿考虑不全,各位大佬轻喷,还望不吝赐教. 首先考虑了一下需要至少四个页面:register.html.register. ...