题意:题目给出了标准strcmp()函数的代码,给你n个单词(n <= 4000, len <= 1000, 大小写字母+数字),问你这些单词两两调用strcmp()函数一共比较了多少次。
分析:本题可以用trie做, 但按照一般的方法建字典树的话,会超时。最坏的情况 ,建立新节点的总体时间为 4000 *1000 * 62 == 2.5 * 10^8 很容易超时。
所以一般的建树方法肯定不行,我们必须优化建立新节点的时间。
建树思路:一个节点的62个儿子可以建立成链表,这样,建立一个新节点时就不用初始化其62个儿子节点了,大大优化了时间。
(我的做法比较繁琐,后来网上有位朋友给我分享了更为简单的做法,建树方法跟我一样,计数方法很容易理解,在我的做法后面)
我的做法:把所有的单词插入trie中,然后根据节点的信息计算所有情况。
我是一个字母一个字母统计其比较个数的
情况分析: 逐个字母比较可分为以下几种
1. 两个不同字母的比较, 如 a, b, 算1次
2. 两个相同的字母的比较, 如 a,a, 算2次
3. 一个字母和一个终止符比较 , 如a, \0, 算1次
4. 两个终止符比较,如 \0, \0, 算2次
具体实现看 代码中的dfs
my dfs:
//val记录的是前缀为 根节点走到当前节点所走过字母 的子串个数和。
//final记录 串为 根节点走到当前节点所走过字母 的个数和
void dfs(int u) {
if(u != 1) ans += (LL) p[u].val * (p[u].val - 1); //情况2,统计一次
ans += (LL) p[u].final * (p[u].final - 1); //情况4, 统计一次
LL tmp = (LL) p[u].final * (p[u].val - p[u].final); // 情况3,统计一次
int j = p[u].child;
while(j != -1) {
tmp += (LL) (p[u].val - p[j].val) * p[j].val; // 这里 情况1 统计两次 和情况3 统计一次
dfs(j);
j = p[j].next;
}
ans += tmp/2; // 情况1,3 都统计了两次,我们必须除以2.
}
网友提供的做法:
我们可以一个串一个串的比较,这要方便很多。也避免了多情况的考虑。
直接上别人的代码,注解是我自己写的。
dfs部分代码:
void dfs(int depth, int u) {
if(head[u] == 0) // 两个串完全相同(终止符也考虑进去了) depth表示长度
ans += tot[u] * (tot[u] - 1) * depth;
else {
int sum = 0;
for(int v = head[u]; v != 0; v = next[v])
sum += tot[v] * (tot[u] - tot[v]); //统计最后一位字母不同的 串有几对(2个为一对)。
ans += sum / 2 * (2 * depth + 1); //相同字母有depth个,要算2次,所以 sum * 2*depth
// +1 加上最后一位是不同字母的个数。
for(int v = head[u]; v != 0; v = next[v])
dfs(depth+1, v);
}
}
my code:
#include <cstdio>
#include <cstring>
#include <cctype>
#define LL long long
struct node {
int val, final, child, next;
int next_char, child_char;
}p[4002 * 1002];
int tot, n;
LL ans;
int idx(char c) { // translate the character into range 0-61.
if(isupper(c)) return c-'A';
if(islower(c)) return c-'a'+26;
return c-'0'+52;
}
void insert(char *s) {
int u = 1, i, j; // "u" means current node
p[u].val++;
for(i = 0; s[i]; i++) {
int k = idx(s[i]);
if(p[u].child == -1) { // if current node has no children, create one.
p[u].child = ++tot;
p[u].child_char = k;
p[tot].next = -1; p[tot].child = -1;
p[tot].val = p[tot].final = 0;
u = tot;
}
else if(p[u].child_char > k) { // if the first child is too big, we need to create a new first child.
p[++tot].next = p[u].child;
p[tot].next_char = p[u].child_char;
p[u].child = tot; p[u].child_char = k;
p[tot].child = -1;
p[tot].val = p[tot].final = 0;
u = tot;
}
else if(p[u].child_char == k) // if the first child exactly matches, just descend.
u = p[u].child;
else { // walk the list of children to find the matching one, create one if needed.
j = p[u].child;
while(p[j].next != -1 && p[j].next_char < k) j = p[j].next;
if(p[j].next != -1 && p[j].next_char == k)
u = p[j].next;
else {
p[++tot].next = p[j].next;
p[tot].next_char = p[j].next_char;
p[j].next = tot; p[j].next_char = k;
p[tot].child = -1;
p[tot].val = p[tot].final = 0;
u = tot;
}
}
p[u].val++;
}
p[u].final++;
}
void dfs(int u) {
// every pair of words that reaches here produces two comparisons.
if(u != 1) ans += (LL) p[u].val * (p[u].val - 1); // pay attention the root, we needn't add it.
ans += (LL) p[u].final * (p[u].final - 1);
// every pair of words that differs exactly here produces one more comparison.
LL tmp = (LL) p[u].final * (p[u].val - p[u].final);
int j = p[u].child;
while(j != -1) {
tmp += (LL) (p[u].val - p[j].val) * p[j].val;
dfs(j);
j = p[j].next;
}
ans += tmp/2;
}
char s[1005];
int main() {
int i, j, cas = 1;
while( ~scanf("%d", &n) && n) {
tot = 1;
p[tot].child = p[tot].next = -1;
p[tot].val = p[tot].final = 0;
while(n--) {
scanf("%s", s);
insert(s);
}
ans = 0;
dfs(1);
printf("Case %d: %lld\n", cas++, ans);
}
return 0;
}