P4683 [IOI2008] Type Printer 打印机
题意描述
[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
#include
#include
#include
#include
#include
#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;insert(a);
if(jl.length()
结语
无耻安利 blog。
简单的 \(trie + dfs\),感觉挺简单的...。
主要是要有题目简化以及将题目转化为抽象数据结构的能力。
完结撒花。