CF633C 字符串
1 CF633C 字符串
2 题目描述
时间限制 \(2s\) | 空间限制 \(256M\)
在观察了 \(Spy Syndrome\) 的结果后,\(Yash\) 发现他的方法有问题。他现在相信像 \(Siddhant\) 这样的超级间谍不会使用像凯撒密码那样的简单、古老的密码。经过了几周对 \(Siddhant\) 句子的观察,\(Yash\) 确定了一种新的加密技术。
对于给定的句子,加密处理为:
- 将句子的所有字母转换为小写;
- 把句子的每个单词都反转;
- 去掉句子中的所有空格。
例如,把句子 \(“Kira\space is\space childish\space and\space he\space hates\space losing”\) 加密后得到的字符串是 \(“ariksihsidlihcdnaehsetahgnisol"\)。
现在,\(Yash\) 得到了一个长度为 \(n\) 的加密后的字符串 \(t\) 和有 \(m\) 个字符串的单词列表。帮助他找出任何只使用列表中的单词组成的原始句子。注意,任何给定的单词都可以在句子中多次使用。
数据范围:\(1 ≤ n ≤ 10000, 1 ≤ m ≤ 100000\)
3 题解
如果我们将 \(t\) 这个字符串反转,那么我们的字符串还要倒着去匹配,十分麻烦。因此我们可以考虑将输入的那 \(m\) 个字符串反转去原串中匹配,但是不能在输入时直接将原串翻转,否则会很难在输出时找到原串。
我们接下来来想算法:很显然,我们要找到匹配方案,我们就要借助于 \(trie\) 树。这道题的思路与 \(L\) 语言这道题十分相似,只是多了一个输出路径。具体细节就是设 \(dp_i\) 表示从第 \(1\) 位到第 \(i\) 位是否存在一种字符串组合方式可以没有重叠和空白地组成 \(t\)。然后用 \(ans\) 数组记录每一个 \(dp\) 值为 \(1\) 的位置中上一个字符串结尾的位置和当前这个字符串在那 \(m\) 个字符串中的编号。字典树的用法就是帮助查看到哪一位会出现字符串的结尾,以确定整个字符串。
输出答案时从第 \(n-1\) 位开始慢慢往前跳转即可。
4 代码(空格警告):
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1000100;
int n, m, tot, p, cnt;
int tre[N][30], ep[N], dp[N];
char n1;
string s;
string t[N];
pair<int, int> ans[N];
int ans1[N];
void insert(string x, int pos)
{
p = 0;
for (int i = x.length()-1; i >= 0; i--)
{
n1 = x[i];
if (n1 >= 'A' && n1 <= 'Z') n1 += 32;
if (!tre[p][n1 - 'a']) tre[p][n1 - 'a'] = ++tot;
p = tre[p][n1 - 'a'];
}
ep[p] = pos;
}
int main()
{
cin >> n >> s >> m;
for (int i = 1; i <= m; i++)
{
cin >> t[i];
insert(t[i], i);
}
for (int i = 0; i < n; i++)
{
if (i == 0 || dp[i-1])
{
p = 0;
for (int j = i; j < n; j++)
{
n1 = s[j];
if (!tre[p][n1 - 'a']) break;
p = tre[p][n1 - 'a'];
if (ep[p])
{
dp[j] = 1;
ans[j] = make_pair(i-1, ep[p]);
}
}
}
}
p = n-1;
while (p >= 0)
{
ans1[++cnt] = ans[p].second;
p = ans[p].first;
}
for (int i = cnt; i >= 1; i--) cout << t[ans1[i]] << " ";
return 0;
}