题解:洛谷 P1481 魔族密码
【题目来源】
【题目描述】
花花:……咦好冷我们现在要解决的是魔族的密码问题(自我陶醉:搞不好魔族里面还会有人用密码给我和菜虫写情书咧,哦活活,当然是给我的比较多拉*^_^*)。
魔族现在使用一种新型的密码系统。每一个密码都是一个给定的仅包含小写字母的英文单词表,每个单词至少包含 \(1\) 个字母,至多 \(75\) 个字母。如果在一个由一个词或多个词组成的表中,除了最后一个以外,每个单词都被其后的一个单词所包含,即前一个单词是后一个单词的前缀,则称词表为一个词链。例如下面单词组成了一个词链:
- i;
- int;
- integer。
但下面的单词不组成词链:
- integer;
- intern。
现在你要做的就是在一个给定的单词表中取出一些词,组成最长的词链,就是包含单词数最多的词链。将它的单词数统计出来,就得到密码了。
风之子:密码就是最长词链所包括的单词数阿……
【输入】
这些文件的格式是,第一行为单词表中的单词数 \(N(1\le N\le 2000)\),下面每一行有一个单词,按字典顺序排列,中间也没有重复的单词。
【输出】
输出共一行,一个整数,表示密码。
【输入样例】
5
i
int
integer
intern
internet
【输出样例】
4
【解题思路】

【算法标签】
《洛谷 P1481 魔族密码》 #树形dp# #哈希,hash# #字典树,Trie# #差分#
【代码详解】
// 动态规划
#include <bits/stdc++.h>
using namespace std;
int n; // 字符串数量
int dp[2005]; // dp数组,dp[i]表示以第i个字符串结尾的最长链长度
int maxlen; // 记录最长链的长度
string s[2005]; // 存储所有字符串
int main()
{
// 输入字符串数量
cin >> n;
// 输入所有字符串
for (int i = 1; i <= n; i++)
{
cin >> s[i];
int maxs = 0; // 记录当前字符串能连接的最大前驱链长度
// 检查前面所有字符串是否可以作为当前字符串的前缀
for (int j = 1; j < i; j++)
{
if (s[i].find(s[j]) == 0) // 如果s[j]是s[i]的前缀
{
maxs = max(maxs, dp[j]); // 更新最大前驱链长度
}
}
dp[i] = maxs + 1; // 当前字符串的最长链长度
maxlen = max(maxlen, dp[i]); // 更新全局最长链长度
}
// 输出最长链长度
cout << maxlen;
return 0;
}
// 字典树
#include <bits/stdc++.h>
using namespace std;
// 字典树节点结构体
struct node
{
char c; // 当前节点字符
map<char, int> child; // 子节点映射(字符->索引)
bool end = 0; // 标记是否为单词结尾
} root; // 根节点
vector<node> trie; // 字典树存储结构
int n, maxs; // n: 字符串数量, maxs: 最大连续前缀数
string s; // 临时存储输入的字符串
/**
* 构建字典树并计算最大连续前缀数
* @param s 要插入的字符串
*/
void build(string s)
{
int fa = 0; // 当前父节点索引(从根节点0开始)
int count = 1; // 当前连续前缀计数
node x; // 新节点临时变量
for (int i = 0; i < s.size(); i++)
{
// 如果当前字符不存在于子节点中
if (!trie[fa].child[s[i]])
{
int len = trie.size(); // 获取新节点位置
trie[fa].child[s[i]] = len; // 添加子节点映射
x.c = s[i]; // 设置节点字符
// 如果是字符串最后一个字符,标记为单词结尾
if (i == s.size() - 1)
{
x.end = count;
}
trie.push_back(x); // 添加新节点
fa = len; // 移动到新节点
}
else
{
// 字符已存在,移动到对应子节点
fa = trie[fa].child[s[i]];
// 如果遇到单词结尾,增加计数并更新最大值
if (trie[fa].end)
{
count++;
maxs = max(maxs, count);
}
}
}
}
int main()
{
cin >> n; // 输入字符串数量
trie.push_back(root); // 初始化字典树根节点
// 处理每个字符串
for (int i = 0; i < n; i++)
{
cin >> s;
build(s); // 构建字典树并计算
}
cout << maxs; // 输出最大连续前缀数
return 0;
}
【运行结果】
5
i
int
integer
intern
internet
4
浙公网安备 33010602011771号