题目
题意
什么阴间题目描述。
给出
n
n
n 个字符串,你需要将他们排成适当的序列使序列中各个串的权值和最小。
序列中的一个字符串
s
i
(
1
≤
i
≤
n
)
s_i\ (1 \le i \le n)
si (1≤i≤n) 权值定义如下:
- 若存在 s j ( j > i ) s_j\ (j > i) sj (j>i) 是 s i s_i si 的后缀,权值为 n 2 n^2 n2;
- 否则,若任意 s j ( j < i ) s_j\ (j < i) sj (j<i) 都不是 s i s_i si 的后缀,权值为 i i i;
- 否则,设 j ( j < i ) j\ (j < i) j (j<i) 是满足 s j s_j sj 是 s i s_i si 后缀的最大的下标,权值为 i − j i - j i−j。
分析
由题意可得一个串 s s s 的后缀必然不能出现在 s s s 的后面,否则代价高于一切。那么将所有 s s s 倒序插入 Trie 中,删除无意义节点后,每个节点视为以它开头的那个串。我们需要设计一个节点序列,使各个节点与它的祖先节点在序列中的最小距离之和最小(当然,祖先必须在该节点前出现),显然需要按 DFS 序,因为节点 u u u 出现过后,如果 u u u 有儿子,下一个出现的必定是 u u u 的某个儿子 v v v,因为这样可以保证 v v v 的代价是 1 1 1,且能够递归下去。再 u u u 过后考虑选哪个儿子 v v v,由于遍历完 v v v 子树过后,对于其他儿子,每个的代价都会增加 v v v 的子树大小,因此子树大小越小的必然越要靠前。于是按子树大小从小到大 DFS 计算代价即可。
代码
#include <bits/stdc++.h>
int Read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9')
c = getchar();
while (c >= '0' && c <= '9')
x = x * 10 + (c ^ 48), c = getchar();
return x;
}
const int MAXN = 510000;
const int MAXL = 510000;
int N;
char S[MAXL + 5];
int Par[MAXN + 5];
int Find(const int &u) {
return (u == Par[u]) ? u : (Par[u] = Find(Par[u]));
}
int Num[MAXN + 5];
int T[MAXN + 5][30], Cnt;
void Insert(char *str, int len, int id) {
int u = 0;
for (int i = len; i >= 1; i--) {
int x = str[i] - 'a';
if (!T[u][x])
T[u][x] = ++Cnt;
u = T[u][x];
}
Num[u] = id;
}
std::vector<int> G[MAXN + 5];
void Build(const int &u, const int &p) {
if (Num[u])
G[p].push_back(Num[u]);
for (int i = 0; i < 26; i++)
if (T[u][i])
Build(T[u][i], Num[u] ? Num[u] : p);
}
int Siz[MAXN + 5];
bool Comp(const int &i, const int &j) {
return Siz[i] < Siz[j];
}
void Init(const int &u) {
Siz[u] = 1;
for (int v: G[u])
Init(v), Siz[u] += Siz[v];
std::sort(G[u].begin(), G[u].end(), Comp);
}
int Dfn;
long long Ans;
void Dfs(const int &u) {
int pos = Dfn++;
for (int v: G[u]) {
Ans += Dfn - pos;
Dfs(v);
}
}
int main() {
N = Read();
for (int i = 1; i <= N; i++) {
scanf("%s", S + 1);
Insert(S, strlen(S + 1), i);
}
for (int i = 1; i <= Cnt; i++)
Par[i] = i;
Build(0, 0), Init(0), Dfs(0);
printf("%lld", Ans);
return 0;
}