2025.7.4 字典树2
2025.7.4 字典树(Trie)
例题
P4583
题目描述
你需要利用一台可移动的打印机打印出 \(n\) 个单词。这种可移动式打印机是一种老式打印机,它需要你将一些小的金属块(每个包含一个字母)放到打印机上以组成单词。然后将这些小金属块压在一张纸上以打印出这个词。这种打印机允许你进行下列操作:
- 在打印机当前词的末端(尾部)添加一个字母;
- 在打印机当前词的尾部删去一个字母(将打印机当前词的最后一个字母删去)。仅当打印机当前至少有一个字母时才允许进行该操作;
- 将打印机上的当前词打印出来。
初始时打印机为空,或者说它不含任何带字母的金属块。打印结束时,允许有部分字母留在打印机内。同时也允许你按照任意的次序打印单词。
由于每一个操作都需要一定时间,所以需要你尽可能减少所需操作的总数目(将操作的总数最小化)。
你需要编写一个程序,给定所要打印的 \(n\) 个单词,找出以任意次序打印所有单词所需操作的最小数目,并输出一种这样的操作序列。
对于 \(100\%\) 的数据,\(1\leq n\leq25000\)。
思路
显然,如果两个单词存在公共前缀,可以少打印一个前缀,而字典树可以求公共最长前缀,所以把所有单词存进字典树中。
注意到在结束时允许有字母留在打印机内,所以最后一定留下最长的单词是最优解,需要找到最长单词并标记。
dfs搜索字典树,注意先搜未被标记的路径,留下最长的单词。回溯处理单词的删除。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,cnt,ans;
string s,t,anss;
bool k[N];
struct node{
	int mp[26],ending;
	char c;
}tr[N];
void add(string s){
	int p=0,ch;
	for(int i=0;i<s.size();i++){
		ch=s[i]-'a';
		if(!tr[p].mp[ch]){
			tr[p].mp[ch]=++cnt;
		}
		p=tr[p].mp[ch];
		tr[p].c=s[i];
	}
	tr[p].ending++;
}
void bj(string t){
	int p=0,ch;
	for(int i=0;i<t.size();i++){
		ch=t[i]-'a';
		p=tr[p].mp[ch];
		k[p]=1;//标记
	}
}
void dfs(int u){
	if(tr[u].ending){
		ans++;
		anss+='P';
	}
	if(ans==n){//直接输出,留下最长单词不删除
		cout<<anss.size()<<endl;
		for(int i=0;i<anss.size();i++){
			cout<<anss[i]<<endl;
		}
		return ;
	}
	for(int i=0;i<26;i++){
		int v=tr[u].mp[i];
		if(v&&!k[v]){//先搜未标记的
			anss+=tr[v].c;
			dfs(v);
			anss+='-'; 
		}
	}
	for(int i=0;i<26;i++){
		int v=tr[u].mp[i];
		if(v&&k[v]){
			anss+=tr[v].c;
			dfs(v);
			anss+='-'; 
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>s;
		add(s);
		if(s.size()>t.size()){//找出最长单词
			t=s;
		}
	}
	bj(t);
	dfs(0);
	return 0;
}
但是 数据有点水
- 很显然,当出现多个连续相同字母时,一个一个打印是慢的,如aaaa的最快打印方法为a a P P,但原本的方法为a a a a P。
- 对于后缀相同的单词也没有考虑,如:
2
abbbb
bbbb
如果考虑这些就难很多了
 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号