标签:需要 结束 print clu while 公共前缀 hack int bit
如果按照题意模拟的话是肯定会超时的(题目都好心告诉你了),考虑优化。
我们发现对于两个串我们只用求它们的最长公共前缀即可。
如果将所有串建一棵\(trie\),那最长公共前缀就对应到它们的结束节点在\(trie\)树上的\(lca\)。所以我们建好\(trie\)后只用遍历一遍所有节点看看它是几个串的\(lca\)即可。
其实只需要一波数学推导即可。
对于一条边\(u->v\),我们统计出子树\(u\)和\(v\)分别有多少结束标记\(sum\),然后对应以\(u\)为lca的串就增加\(sum[v] \times (sum[u] - sum[v])\)。
然后我们发现这是成对出现的所以要除以\(2\)。
最后加上上述值乘上根到当前节点的距离乘\(2\)加\(1\)即可。
如果当前节点是多个串的结尾,那还要考虑这些串互相的贡献。其实也很好考虑。
设\(End[x]\)代表节点\(x\)有多少串在这结尾,则答案会加上\((End[x] \times (End[x] - 1) / 2) * ((dis + 1) \times 2)\)。
但是写完之后交上去发现错了。
可参考hack数据:
2
app
apple
原因是直接\(sum[v] \times (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\n", num, ans);
}
return 0;
}
标签:需要 结束 print clu while 公共前缀 hack int bit
原文地址:https://www.cnblogs.com/zcr-blog/p/14901180.html