题意描述

[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. Java知识系统回顾整理01基础04操作符02关系操作符

    一.关系操作符 关系操作符:比较两个变量之间的关系  > 大于 >= 大于或等于 < 小于 <= 小于或等于 == 是否相等 != 是否不等 public class Hell ...

  2. matlab中repmat函数的用法

    转载:https://blog.csdn.net/facetosea1/article/details/83573859 B = repmat(A,m,n)B = repmat(A,[m n])B = ...

  3. 【题解】CF1426D Non-zero Segments

    题目戳我 \(\text{Solution:}\) 若\([l,r]\)子段和是\(0,\)则\(sum[r]=sum[l-1].\) 于是我们可以考虑维护当前哪一个前缀和出现过.对于区间\([l,r ...

  4. Nginx如何部署静态web项目

    环境准备 windows nginx web项目资源包 准备资源包 这里拿layuimini项目举例,从码云上克隆下来直接访问提示需要部署在web服务器当中才能正常浏览演示 准备Nginx 进入解压后 ...

  5. postgreSQL与Kingbase 字符串裁剪区别

    --postgreSQL postgres=# select substring('abcdefg',0,4); substring abc (1 行记录) postgres=# select sub ...

  6. centos7 samba安装教程

    samba的用途:有的时候,我们需要在centos7 的文件能共享给其他机器. rpm -qa|grep samba yum install -y samba setenforce 0 sed -i ...

  7. Oracle体系结构概述与SQL解析剖析

    Oracle服务器 是一个数据库管理系统,它提供了一种全面.开放.集成的方法来管理信息. Oracle服务器由Oracle数据库和Oracle实例组成. oracle数据库软件和Oracle数据库软件 ...

  8. 微信小程序中使用 npm包管理 (保姆式教程)

    打开自己的微信小程序项目,在勾选这个选项 然后在第一次应该是失败的提示"没有找到可以构建的npm包". 在 小程序的根目录下比如我的项目如图: 右击鼠标在终端中打开. 然后输入:n ...

  9. Android开发Settings源码分析之主界面加载(二)

    现在都说互联网寒冬,其实只要自身技术能力够强,咱们就不怕!我这边专门针对Android开发工程师整理了一套[Android进阶学习视频].[全套Android面试秘籍].[Android知识点PDF] ...

  10. 在VC6.0下运行C语言程序,以及编程入门必备的常识类小知识!

    今天给大家分享在VC6.0环境下编写C语言程序的基本步骤,为初学者打开学习C语言的第一道门.具体步骤如下(如果需要软件资源,可以留言): 1)新建工作区 依次点击 文件--新建--工作区 或是Ctrl ...