背单词
逆序 Trie 建后缀森林,子树升序定序
正文
// 原题:https://oj.yecheng.tv/p/T1477
// 题意:n 个互不相同的单词排成 1..n 的填表顺序,每个单词必须排在其所有后缀单词之后,代价为「自己的位置 - 最后一个后缀的位置」(无后缀时代价为位置本身),求最小总代价。
// 思路:单词逆序建 Trie,每个单词结点挂到「最近的单词结尾祖先」下形成一棵森林(该祖先即最长真后缀)。总代价 = 所有位置之和 - 各点父结点位置之和,用 DFS 给每棵子树分配连续区间,并把兄弟按子树大小升序排列即可最小化。
// 复杂度:O(总字符数 + n log n) 时间 / O(总字符数) 空间
// 易错点:后缀父子关系必须在全部单词插完后统一由 Trie 父亲推出,插入时顺手记录会因后面的短单词还没进树而挂错父亲。
// 易错点:森林里只放「单词结尾结点」,中间结点不能参与排位;兄弟要按子树大小升序排且 DFS 区间必须连续。
#include <bits/stdc++.h>
using namespace std;
struct Node {
int ch[26];
bool ed;
Node(){
memset(ch, 0, sizeof(ch));
ed = false;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if(!(cin >> n)) return 0;
vector<Node> tr;
tr.reserve(510000 + 5);
tr.push_back(Node());
vector<int> tpar(1, 0);
vector<int> par(1, 0);
vector<char> isEnd(1, 0);
vector<int> wordNode(n, 0);
for(int i = 0; i < n; i++){
string w;
cin >> w;
reverse(w.begin(), w.end());
int u = 0;
for(int j = 0; j < (int)w.size(); j++){
int c = w[j] - 'a';
int v = tr[u].ch[c];
if(!v){
v = (int)tr.size();
tr[u].ch[c] = v;
tr.push_back(Node());
tpar.push_back(u);
par.push_back(0);
isEnd.push_back(0);
}
u = v;
}
isEnd[u] = 1;
wordNode[i] = u;
}
int sz = (int)tr.size();
for(int u = 1; u < sz; u++){
par[u] = isEnd[tpar[u]] ? tpar[u] : par[tpar[u]];
}
vector<vector<int>> kids(sz);
vector<int> sub(sz, 0);
for(int u = 1; u < sz; u++){
if(!isEnd[u]) continue;
kids[par[u]].push_back(u);
sub[u] = 1;
}
for(int u = sz - 1; u >= 1; u--){
if(!isEnd[u]) continue;
sub[par[u]] += sub[u];
}
for(int u = 0; u < sz; u++){
sort(kids[u].begin(), kids[u].end(), [&](int a, int b){
return sub[a] < sub[b];
});
}
vector<int> pos(sz, 0);
vector<int> idx(sz, 0);
vector<int> st;
st.push_back(0);
int timer = 0;
while(!st.empty()){
int u = st.back();
if(idx[u] == 0 && u != 0) pos[u] = ++timer;
if(idx[u] < (int)kids[u].size()){
st.push_back(kids[u][idx[u]++]);
}else{
st.pop_back();
}
}
long long ans = 0;
for(int i = 0; i < n; i++){
int u = wordNode[i];
ans += (long long)pos[u] - (par[u] ? pos[par[u]] : 0);
}
cout << ans << "\n";
return 0;
}
一本通·提高篇的其它内容
- 活动安排
- 种树
- 喷水装置
- 加工生产调度
- 智力大冲浪
- 数列极差
- 数列分段
- 线段
- 家庭作业
- 钓鱼
- 糖果传递
- 愤怒的牛
- Best Cow Fences
- 曲线
- 数列分段 II
- 扩散
- 灯泡
- 传送带
- 数的划分
- 生日蛋糕
- 小木棍
- Addition Chains
- 埃及分数
- 平板涂色
- 质数方阵
- 靶形数独
- 电路维修
- 魔板
- Knight Moves
- 棋盘游戏
- Keyboarding
- 移动玩具
- 山峰和山谷
- Oulipo
- 图书管理
- Power Strings
- Seekthe Name, Seek the Fame
- Friends
- A Horrible Poem
- Beads
- Antisymmetry
- 门票
- 收集雪花
- 剪花布条
- Power Strings
- Radio Transmission
- OKR-Periods of Words
- 似乎在梦中见过的样子
- Censoring
- Phone List
- The XOR Largest Pair
- Nikitosh 和异或
- Immediate Decodability
- L 语言
- Secret Message 秘密信息
- The Xor-longest Path
- Keywords Search
- 玄武密码
- Censoring
- 单词