P4683 [IOI2008] Type Printer 打印机
题意描述
几百年前的 IOI 的题目还是很好的呀。
给你一个 诡异的 打印机,它只能用已有的字符来打印,而且必须每一个都用到。(这岂不是活字印刷术)
你可以对其执行三个操作:
- 打印,用大写字母 P 表示,输出顺序任意,但仅能且必须用到当前打印机里的每一个字符。
- 插入,输入一个字符 c,表示在打印机中插入字符 c。(打印机的存储是一个队列)
- 删除,从队列尾部删除一个字符。
给定 \(N\) 个字符串,问当前需要至少多少步才能完成所有打印。
算法分析
考虑可爱的 \(trie\) 树,先建一棵树,假设当前已经建好了。(如果不会 你做这道题干什么 可以看看这篇文章)
然后我们发现题目变成了:确定一个 \(DFS\) 的顺序,使得树上的每一个点都遍历到,并且结束于某个叶节点。
思想一
显然,倘若要求回到根节点,步数永远为 \(2\times (N-1)\)。(每条边都经过两次)
但是最后一条路径可以不回去,假设最后打印的字符串长度为 \(len\),显然最终遍历步数为 \(2\times (N-1)-len\)。
注意:这里的遍历步数 \(\neq\) 答案步数,因为答案中还有删除操作。
显然 \(ans_{min}=2\times (N-1)-len_{max}\)。
思想二
既然每一个单词都要输出,打印的操作次数一定 = 总字符串数。
那么关键就是插入与删除次数尽量少,那么显然倘若要求删除次数尽量少,之前插入的字符长度应当尽量小。
所以最长的单词当然要最后打印。
当然以上的证明是不严谨的,但是有助于大家理解。
所以通过两种方法都可以确定贪心策略:最后走最长的单词,其他随意。
代码实现
其实还挺简单的。
- 建树。不用讲解吧。
- 标记。标记一下最长的字符串,也很简单。
- 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 打印机的更多相关文章
- bzoj AC倒序
Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...
- Python调用打印机参考例子
参考资料: http://blog.csdn.net/jdh99/article/details/42585987 http://www.oschina.net/question/1438043_23 ...
- Spring学习笔记:Spring动态组装打印机
一.如何开发一个打印机 1.可灵活配置使用彩色魔盒或灰色魔盒 2.可灵活配置打印页面的大小 二.打印机功能的实现依赖于魔盒和纸张 三.步骤: 1.定义墨盒和纸张的接口标准 package cn.pri ...
- C# 调用打印机打印文件
C# 调用打印机打印文件,通常情况下,例如Word.Excel.PDF等可以使用一些对应的组件进行打印,另一个通用的方式是直接启用一个打印的进程进行打印.示例代码如下: using System.Di ...
- LDAP注入与防御解析
[目录] 0x1 LDAP介绍 0x2 LDAP注入攻击及防御 0x3 参考资料 0x1 LDAP介绍 1 LDAP出现的背景 LDAP(Lightweight Directory Access Pr ...
- 0031 Java学习笔记-梁勇著《Java语言程序设计-基础篇 第十版》英语单词
第01章 计算机.程序和Java概述 CPU(Central Processing Unit) * 中央处理器 Control Unit * 控制单元 arithmetic/logic unit /ə ...
- Windows WMIC命令使用详解
本文转载出处http://www.jb51.net/article/49987.htm www.makaidong.com/博客园文/32743.shtml wmic alias list brief ...
- Java基础常见英语词汇
Java基础常见英语词汇(共70个) ['ɔbdʒekt] ['ɔ:rientid]导向的 ['prəʊɡræmɪŋ]编程 OO: object ...
- IT软件开发常用英语词汇
Aabstract 抽象的abstract base class (ABC)抽象基类abstract class 抽象类abstraction 抽象.抽象物.抽象性access 存取.访问access ...
随机推荐
- Flink深入浅出: 应用部署与原理图解(v1.11)
往期推荐: Flink深入浅出:内存模型 Flink深入浅出:JDBC Source从理论到实战 Flink深入浅出:Sql Gateway源码分析 Flink深入浅出:JDBC Connector源 ...
- 【Excel技巧】用IF函数进行等级评定
如果下面给出一份"2月份语文成绩考核表",那么如何对成绩进行等级评定呢. 等级评定规则: 总分(100分) A级(91-100) B级(81-90) C级(71-80) D级(70 ...
- 每日一题 LeetCode 679. 24点游戏 【递归】【全排列】
题目链接 https://leetcode-cn.com/problems/24-game/ 题目说明 题解 主要方法:递归 + 全排列 解释说明: 将 4 个数进行组合形成算式,发现除了 (a❈b) ...
- AMBuild
什么是AMBuild? AMBuild是构建软件项目和创建发布包的工具.它是针对C++项目的,当然也可以用于其它任何语言的项目,它主要针对解决大多数构建工具所解决不了的三个大问题: 1.准确性:不需要 ...
- linux的bootmem内存管理
内核刚开始启动的时候如果一步到位写一个很完善的内存管理系统是相当麻烦的.所以linux先建立了一个非常简单的临时内存管理系统bootmem,有了这个bootmem就可以做简单的内存分配/释放操作,在b ...
- Android HandlerThread 详解
概述 HandlerThread 相信大家都比较熟悉了,从名字上看是一个带有 Handler 消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是Handler + Thread 的结合, ...
- 基于python常用排序与查找
""" 排序与查找 -- 冒泡排序 -- 选择排序 -- 快速排序 --****经典 -- 希尔排序 """ # 常用排序的实现 # 冒泡排 ...
- 四年了自学了C/C++那么久,还写不出项目,正常吗?
前言: 这是之前在V2EX职场话题里看到的一个话题,类似的小编身边人呢也有相似的困扰. 现在大学里基本都开设了计算机课程,看了那么多相关知识性的书,但学了四年出来,仍然写不出项目,这肯定是有问题的. ...
- php进程 swoole
<?php $pid = posix_getpid(); $ppid = posix_getppid(); var_dump($pid); cli_set_process_title(" ...
- 学了那么多 NoSQL 数据库 NoSQL 究竟是啥
NoSQL 简史 NoSQL 一词最早出现于 1998 年,是 Carlo Strozzi 开发的一个轻量.开源.不提供 SQL 功能的关系数据库. 2009 年,Last.fm 的 Johan Os ...