题意描述

[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. mysql-16-variables

    #变量 /* 系统变量: 全局变量 会话变量 自定义变量: 用户变量 局部变量 */ # 一.系统变量 #由系统提供,属于服务器层面 #1.查看所有的系统变量 show global variable ...

  2. Python练习题 043:Project Euler 015:方格路径

    本题来自 Project Euler 第15题:https://projecteuler.net/problem=15 ''' Project Euler: Problem 15: Lattice p ...

  3. 大话Python类语义

    类 物以类聚,人以群分,就是相同特征的人和事物会自动聚集在一起,核心驱动点就是具有相同特征或相类似的特征,我们把具有相同特征或相似特征的事物放在一起,被称为分类,把分类依据的特征称为类属性 计算机中分 ...

  4. 记一次ElementUI源码修改过程

    修改目的 使用ElementUI el-tree过程发现选中节点,键盘移动上下键时(key down\key up)el-tree默认高亮移动的节点,业务上需要重写此事件. ​从官网发现该事件没有暴露 ...

  5. 041 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 03 案例演示while循环的使用——求1到5的累加和

    041 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 03 案例演示while循环的使用--求1到5的累加和 本文知识点:案例演示while循环的使用1 ...

  6. CF877E Danil and a Part-time Job

    题目大意: link 有一棵 n 个点的树,根结点为 1 号点,每个点的权值都是 1 或 0 共有 m 次操作,操作分为两种 get 询问一个点 x 的子树里有多少个 1 pow 将一个点 x 的子树 ...

  7. Winsock 编程详解

    转载请注明出处!本文地址:https://www.cnblogs.com/teternity/p/WinSock.html Winsock 编程 目录 通用函数讲解 WSAStartup WSACle ...

  8. 基于空镜像scratch创建一个新的Docker镜像

    我们在使用Dockerfile构建docker镜像时,一种方式是使用官方预先配置好的容器镜像.优点是我们不用从头开始构建,节省了很多工作量,但付出的代价是需要下载很大的镜像包. 比如我机器上docke ...

  9. 机器学习算法——kNN(k-近邻算法)

    算法概述 通过测量不同特征值之间的距离进行 [分类] 优点:精度高.对异常值不敏感.无数据输入假定. 缺点:计算复杂度高.空间复杂度高. 适用数据范围: 数值型 和 标称型 . 算法流程 数据 样本数 ...

  10. python之线程池和进程池

    线程池和进程池 一.池的概念 池是用来保证计算机硬件安全的情况下最大限度的利用计算机 它降低了程序的运行效率但是保证了计算机硬件的安全从而让你写的程序能够正常运行 ''' 无论是开设进程也好还是开设线 ...