题意描述

[IOI2008] Type Printer 打印机

几百年前的 IOI 的题目还是很好的呀。

给你一个 诡异的 打印机,它只能用已有的字符来打印,而且必须每一个都用到。(这岂不是活字印刷术)

你可以对其执行三个操作:

  1. 打印,用大写字母 P 表示,输出顺序任意,但仅能且必须用到当前打印机里的每一个字符。
  2. 插入,输入一个字符 c,表示在打印机中插入字符 c。(打印机的存储是一个队列)
  3. 删除,从队列尾部删除一个字符。

给定 \(N\) 个字符串,问当前需要至少多少步才能完成所有打印。

算法分析

考虑可爱的 \(trie\) 树,先建一棵树,假设当前已经建好了。(如果不会 你做这道题干什么 可以看看这篇文章

然后我们发现题目变成了:确定一个 \(DFS\) 的顺序,使得树上的每一个点都遍历到,并且结束于某个叶节点。

思想一

显然,倘若要求回到根节点,步数永远为 \(2\times (N-1)\)。(每条边都经过两次)

但是最后一条路径可以不回去,假设最后打印的字符串长度为 \(len\),显然最终遍历步数为 \(2\times (N-1)-len\)。

注意:这里的遍历步数 \(\neq\) 答案步数,因为答案中还有删除操作

显然 \(ans_{min}=2\times (N-1)-len_{max}\)。

思想二

既然每一个单词都要输出,打印的操作次数一定 = 总字符串数。

那么关键就是插入与删除次数尽量少,那么显然倘若要求删除次数尽量少,之前插入的字符长度应当尽量小。

所以最长的单词当然要最后打印。

当然以上的证明是不严谨的,但是有助于大家理解。

所以通过两种方法都可以确定贪心策略:最后走最长的单词,其他随意。

代码实现

其实还挺简单的。

  1. 建树。不用讲解吧。
  2. 标记。标记一下最长的字符串,也很简单。
  3. dfs。最重要的环节了。在 dfs 时主要有三种操作:打印,向下遍历,回溯。

在打印时通过判断当前是否为单词的尾部来进行判断。

向下遍历时特殊处理被标记的节点。

回溯时倘若该点不是被标记的点,就打印 "-"。

看不懂的话看代码吧:

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<string>
#define N 500010//几次空间开小了都 RE,所以索性开大一点,dalao 勿喷。
using namespace std; int n,trie[N][30],tot=1;
bool sum[N],flag[N],finish=false;
//上面三个分别用来标记:是否是单词结尾(trie 基本操作),是否是最长字符串上的点,是否到了最后一个单词。
string a,jl,ans; void insert(string a){
int p=1;
for(int i=0;i<a.length();i++){
int ch=a[i]-'a';
if(!trie[p][ch]) trie[p][ch]=++tot;
p=trie[p][ch];
}
sum[p]=true;
return;
}
//常规插入操作。
void Mark(string a){
int p=1;
for(int i=0;i<a.length();i++){
int ch=a[i]-'a';
p=trie[p][ch];
flag[p]=true;
}
return;
}
//标记操作,顺着 trie 走一遍就好了。
void dfs(int now){
if(sum[now]){
ans+='P';//打印情况。
//注意这里一定不要写 return;
//因为有可能这是一个单词的前缀,这样就少了一个甚至更多的单词。
}
int x=-1;
for(int i=0;i<26;i++){
int t=trie[now][i];
if(!t) continue;
if(flag[t]) x=i;//记录最长串上的点,最后遍历。
else{
ans+=(i+'a');
dfs(t);
}
}
if(x!=-1){
ans+=(x+'a');
dfs(trie[now][x]);
}//最后遍历的最长串。
if(flag[now] && x==-1)//当遍历到了最长串的最后一个点,就不用再回退(删除)了。
finish=true;
if(!finish) ans+='-';//回溯时删除。
return;
} int main(){
scanf("%d",&n);
memset(flag,false,sizeof(flag));
memset(sum,false,sizeof(sum));
memset(trie,0,sizeof(trie));//不必要的初始化。
for(int i=1;i<=n;i++){
cin >> a;insert(a);
if(jl.length()<a.length()) jl=a;
}//寻找最长串。
Mark(jl);
dfs(1);
printf("%d\n",ans.length());
for(int i=0;i<ans.length();i++)
printf("%c\n",ans[i]);
return 0;
}

结语

无耻安利 blog

简单的 \(trie + dfs\),感觉挺简单的...。

主要是要有题目简化以及将题目转化为抽象数据结构的能力。

完结撒花。

P4683 [IOI2008] Type Printer 打印机的更多相关文章

  1. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  2. Python调用打印机参考例子

    参考资料: http://blog.csdn.net/jdh99/article/details/42585987 http://www.oschina.net/question/1438043_23 ...

  3. Spring学习笔记:Spring动态组装打印机

    一.如何开发一个打印机 1.可灵活配置使用彩色魔盒或灰色魔盒 2.可灵活配置打印页面的大小 二.打印机功能的实现依赖于魔盒和纸张 三.步骤: 1.定义墨盒和纸张的接口标准 package cn.pri ...

  4. C# 调用打印机打印文件

    C# 调用打印机打印文件,通常情况下,例如Word.Excel.PDF等可以使用一些对应的组件进行打印,另一个通用的方式是直接启用一个打印的进程进行打印.示例代码如下: using System.Di ...

  5. LDAP注入与防御解析

    [目录] 0x1 LDAP介绍 0x2 LDAP注入攻击及防御 0x3 参考资料 0x1 LDAP介绍 1 LDAP出现的背景 LDAP(Lightweight Directory Access Pr ...

  6. 0031 Java学习笔记-梁勇著《Java语言程序设计-基础篇 第十版》英语单词

    第01章 计算机.程序和Java概述 CPU(Central Processing Unit) * 中央处理器 Control Unit * 控制单元 arithmetic/logic unit /ə ...

  7. Windows WMIC命令使用详解

    本文转载出处http://www.jb51.net/article/49987.htm www.makaidong.com/博客园文/32743.shtml wmic alias list brief ...

  8. Java基础常见英语词汇

    Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的                             ['prəʊɡræmɪŋ]编程 OO: object ...

  9. IT软件开发常用英语词汇

    Aabstract 抽象的abstract base class (ABC)抽象基类abstract class 抽象类abstraction 抽象.抽象物.抽象性access 存取.访问access ...

随机推荐

  1. makefile实验二 对目标的深入理解 以及rebuild build clean的实现

    (一) rebuild build clean的实现 新知识点: 当一个目标的依赖是一个伪目标时,这个伪目标的规则一定会被执行. 贴实验代码 CC := gcc Target := helloworl ...

  2. 089 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 使用包进行类管理(1)——创建包

    089 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  3. 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 02 封装的代码实现

    088 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 02 封装的代码实现 本文知识点:Java封装的代码实现 说明:因为时间紧张,本人写博客过程中只 ...

  4. 06 C语言变量

    C语言变量 变量的本质 变量的本质其实是程序可操作的存储区的名称. C 中每个变量都有特定的类型,类型决定了变量存储的大小的范围,在范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以 ...

  5. 题解【QTree3】

    题目描述 给出N个点的一棵树(N-1条边),节点有白有黑,初始全为白 有两种操作: 0 i : 改变某点的颜色(原来是黑的变白,原来是白的变黑) 1 v : 询问1到v的路径上的第一个黑点,若无,输出 ...

  6. js 正则表达式 判断val是不是整数

    function isIntNum(val){ var regPos = / ^\d+$/; // 非负整数 // var regNeg = /^\-[1-9][0-9]*$/; // 负整数 if( ...

  7. chattr 和 lsattr 命令详解

    lsattr 命令 lsattr 命令用于查看文件的第二扩展文件系统属性. 语法: lsattr(选项)(参数) 选项: -E:可显示设备属性的当前值,但这个当前值是从用户设备数据库中获得的,而不是从 ...

  8. 如何获取value值,获取属性值,设置属性值,

    1.获取select下拉框的value值,   2.获取select  的tid值 3.设置属性值  4.判断哪个单选框选中了 prop好像是判断的意思吧,个人理解勿喷谢谢!!!!!!

  9. redis协议规范

    好多年前看过redis的代码,那个时候还是2.6的版本,集群和哨兵还没加入正式代码,这几年redis发展的好快.简略翻译一篇文章redis的https://redis.io/topics/protoc ...

  10. 手把手搭建一个属于自己的在线 IDE

    背景 这几个月在公司内做一个跨前端项目之间共享组件/区块的工程,主要思路就是在 Bit 的基础上进行开发.Bit 主要目的是实现不同项目 共享 与 同步 组件/区块,大致思路如下: 在 A 项目中通过 ...