UVa 11732 trie好题

题目链接

题意:题目给出了标准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;
}





  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值