题解:P9694 [GDCPC2023] New but Nostalgic Problem
OrangeRainee · · 题解
Solution
Trie。
假设此时已经确定答案前三位为
要求字典序最小,从
假设当前枚举到字母
- 对于
\forall \texttt{abc}_{\texttt{a} \sim \texttt{e} } \dots 可选。因为他们 LCP 一定小于等于\texttt{abce} \dots 。 - 对于
\texttt{abc}_{\texttt{f} \sim \texttt{z}} \dots ,如果存在那么每个顶多选一个。
举个例子:
如果我们选择大于等于两个
\texttt{abcf} \dots ,那么此时最大 LCP 成为\texttt{abcf} \dots ,不再是\texttt{abce} \dots 。
- 剩下字符串可选情况维持原状。
以上,在合法情况下我们一定尽量多选。
如果此时我们已经可以选至少
在确定第四位时,我们需要确定答案是否就为四位。
考虑我们现在已经确定答案为
- 所有
\texttt{abce}_{\texttt{a} \sim \texttt{z}} \dots 字符串,我们每种只能最多选择一个。否则答案大于当前确定的答案。 - 剩下字符串可选情况维持原状。
如果此时我们能够选至少
重复直到可判定答案合法。
以上策略保证答案最优,即 LCP 最小。若存在比当前枚举的答案更优的合法方案,一定会在之前被枚举到。
在 Trie 上进行这一过程,时间复杂度为
Code
#include <bits/stdc++.h>
using namespace std;
int t;
#define maxn 1000005
int ed[maxn], cnt[maxn];
int tr[maxn][27], tot = 1;
void add(char *s, int len) {
int p = 1;
++cnt[p];
for (int i = 1; i <= len; ++i) {
int u = s[i] - 'a' + 1;
if (!tr[p][u]) {
tr[p][u] = ++tot;
memset(tr[tot], 0, sizeof tr[tot]); // 插入时顺便清空 trie
}
p = tr[p][u];
++cnt[p]; // 记录经过当前位置的字符串个数
}
++ed[p]; // 记录从当前位置结束的字符串个数
return;
}
char s[maxn];
void sol() {
// 多测记得清空
tot = 1;
for (int i = 1; i <= 26; ++i)
tr[1][i] = 0; // 清空首层
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
int len = strlen(s + 1);
add(s, len);
}
int p = 1;
// 题解大部分写的是 dfs,这里用简单循环实现
while (1) {
int sum = ed[p];
for (int i = 1; i <= 26; ++i)
if (cnt[tr[p][i]])
++sum; // 下一位每个字符串最多选一个
if (sum >= k) {
// 判定答案合法
for (int i = 1; i <= tot; ++i) {
ed[i] = 0;
cnt[i] = 0;
}
if (p == 1) // 在首层,即答案为空字符串
printf("EMPTY");
putchar('\n');
// 已经找到最优合法答案,结束程序即可
return;
}
for (int i = 1; i <= 26; ++i) {
if (!cnt[tr[p][i]])
continue;
sum += cnt[tr[p][i]] - 1;
// 之所以减一,因为上面已经每个算了一个
if (sum >= k) {
// 确定下一位字母
k -= (sum - cnt[tr[p][i]]);
// 把当前可选所有字符串数从 k 中减掉
putchar((char)('a' + i - 1));
// 已确定,直接输出即可
p = tr[p][i];
break; // 进入下一位的枚举
}
}
}
return;
}
int main() {
scanf("%d", &t);
while (t--)
sol();
return 0;
}