P3294 [SCOI2016] 背单词 题解
题意
给你
设当前字符串为
-
如果
存在后缀且 的后缀在 之后,花费加 。 -
如果
不存在后缀则花费加 。 -
设
为 之前离其最近的是 的后缀的字符串的位置, 存在后缀且 的后缀在 之前,则花费加 。
解法
对于 1 条件,显然尽量不要满足,因为 1 条件的花费永远比 2、3 条件多。
对于 2 条件,可以视为 3 条件中
所以可以有一个贪心策略,对于一个字符串
题目需要维护后缀,那么可以考虑将字符串反转,变成前缀,于是也可以想到对于反转的字符串建立 Trie 树。
既然要满足贪心策略,那么在 Trie 中,每一个字符串结尾的结点要在它的祖先结点之后。Trie 树就可以维护排列中的先后关系。
3 条件的花费为
考虑另一个贪心策略,由于 3 条件花费为
由于我们需要
此时我们得到最优的遍历顺序为 dfs 序,对于一个结点
AC Code
#include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e6 + 5; int n, trie[N][27], cnt, last[N], siz[N], dfn, ans; bool End[N]; vector<int> e[N]; void insert(string s) { int x = 0; for(int _ = 0; _ < s.size(); _++) { char i = s[_]; if(!trie[x][i - 'a']) trie[x][i - 'a'] = ++cnt; x = trie[x][i - 'a']; } End[x] = true; return; } void rebuild(int x) { if(End[x] && x) { e[last[x]].push_back(x); last[x] = x; } for(auto y : trie[x]) { if(!y) continue; last[y] = last[x]; rebuild(y); } return; } void dfs(int x) { siz[x] = 1; for(auto y : e[x]) { dfs(y); siz[x] += siz[y]; } sort(e[x].begin(), e[x].end(), [](int x, int y) { return siz[x] < siz[y]; }); return; } void dfs1(int x) { int k = dfn; dfn++; for(auto y : e[x]) { ans += dfn - k; dfs1(y); } return; } signed main() { cin >> n; for(int i = 1; i <= n; i++) { string s; cin >> s; reverse(s.begin(), s.end()); insert(s); } End[0] = true; rebuild(0), dfs(0), dfs1(0); cout << ans; return 0; }
本文作者:Luckies
本文链接:https://www.cnblogs.com/Luckies/p/18322774/P3294
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步