如果按照题意模拟的话是肯定会超时的(题目都好心告诉你了),考虑优化。
我们发现对于两个串我们只用求它们的最长公共前缀即可。
如果将所有串建一棵(trie),那最长公共前缀就对应到它们的结束节点在(trie)树上的(lca)。所以我们建好(trie)后只用遍历一遍所有节点看看它是几个串的(lca)即可。
其实只需要一波数学推导即可。
对于一条边(u->v),我们统计出子树(u)和(v)分别有多少结束标记(sum),然后对应以(u)为lca的串就增加(sum[v] imes (sum[u] - sum[v]))。
然后我们发现这是成对出现的所以要除以(2)。
最后加上上述值乘上根到当前节点的距离乘(2)加(1)即可。
如果当前节点是多个串的结尾,那还要考虑这些串互相的贡献。其实也很好考虑。
设(End[x])代表节点(x)有多少串在这结尾,则答案会加上((End[x] imes (End[x] - 1) / 2) * ((dis + 1) imes 2))。
但是写完之后交上去发现错了。
可参考hack数据:
2
app
apple
原因是直接(sum[v] imes (sum[u] - sum[v]))并不是所有乘 (2),与结尾刚好到(x)的串其实就只有一次,所以要再额外考虑。
Code:
#include <bits/stdc++.h>
using namespace std;
int trie[4000010][62], tot = 1;
char s[1010];
long long End[4000010], sum[4000010];
int n;
long long ans;
int num;
int f(char ch) {
if (isdigit(ch)) {
return ch - '0';
}
if ('a' <= ch && ch <= 'z') {
return ch - 'a' + 10;
}
return ch - 'A' + 36;
}
void dfs(int x, int dis) {
long long cnt = 0;
for (int i = 0; i < 62; i++) {
if (trie[x][i]) {
cnt += sum[trie[x][i]] * (sum[x] - sum[trie[x][i]] - End[x]);
cnt += End[x] * sum[trie[x][i]] * 2;
dfs(trie[x][i], dis + 1);
trie[x][i] = 0;
}
}
cnt >>= 1;
ans += cnt * ((dis << 1) + 1);
ans += (End[x] * (End[x] - 1) >> 1) * ((dis + 1) << 1);
End[x] = 0;
sum[x] = 0;
}
int main() {
while (scanf("%d", &n) != EOF && n) {
num++;
tot = 1;
for (int i = 1, len, p; i <= n; i++) {
scanf("%s", s + 1);
len = strlen(s + 1);
p = 1;
for (int j = 1; j <= len; j++) {
sum[p]++;
if (!trie[p][f(s[j])]) trie[p][f(s[j])] = ++tot;
p = trie[p][f(s[j])];
}
sum[p]++;
End[p]++;
}
ans = 0;
dfs(1, 0);
printf("Case %d: %lld
", num, ans);
}
return 0;
}