UVA 11732-"strcmp()" Anyone?-trie(左儿子右兄弟表示法(省空间))

本题数据略大,显然不能用普通的儿子节点法建立trie,因此采用左儿子右兄弟表示法建树,其实就是二叉树表示法,本质就是把原来的26叉树转成2叉树,时间复杂度常数增大,空间复杂度尽可能减小

我们知道26叉树中,如果数据不那么变态,应该是大部分节点都是浪费掉的,而使用二叉树建树,能充分利用空间,缺点就是 原来访问某个节点的子节点 如果查询子节点中有没‘a’字符,我们只需要访问ch[u][0]即可,O(1),而在二叉树中,我们得遍历他所有的儿子节点,最差的情况是O(26)

当然,如果数据出到了最极端的情况,那么两种方法就没什么区别了。


本题按照LRJ的做法 是把单词包括最后的‘\0’都插入字典树了。。其实也可以不插入。就是要判断一下,我2种方法都写了一下,时间差不多

分别是 512ms,492ms

插入‘\0’:

// UVa11732 strcmp() Anyone?
// Yuhong Liu
#include<cstring>
#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxnode = 4005 * 1005 + 10; 
int  ok,ok2; 
__int64      ans; // 答案
// 字母表为全体小写字母的Trie	
struct Trie 
{
	int head[maxnode]; // head[i]为第i个结点的左儿子编号
	int next[maxnode]; // next[i]为第i个结点的右兄弟编号
	
	char ch[maxnode];  // ch[i]为第i个结点上的字符
	int tot[maxnode];  // tot[i]为第i个结点为根的子树包含的叶结点总数
	int sz; // 结点总数
	
	void clear() { sz = 1; tot[0] = head[0] = next[0] = 0; } // 初始时只有一个根结点
	
	// 插入字符串s(包括最后的'\0'),沿途更新tot
	void insert(const char *s) 
	{
		int u = 0, v, n = strlen(s);
		tot[0]++;
		for(int i = 0; i <=n; i++) 
		{
			// 找字符a[i]
			bool found = false;
			for(v = head[u]; v != 0; v = next[v])
			{
				if(ch[v] == s[i]) 
				{ // 找到了
					found = true;
					break;
				}
			}
			if(!found)
			{
				v = sz++; // 新建结点
				tot[v] = 0;
				ch[v] = s[i];
				next[v] = head[u];
				head[u] = v; // 插入到链表的首部
				head[v] = 0;
			}
			
			tot[v]++;
			u = v;
		} 
	} 
	
	
	// 统计
	__int64   cal(int depth,int u) 
	{
		if (ch[u]=='\0'&&u!=0) 
		{
			ans+=tot[u]*(tot[u]-1)/2 *(2*depth);
			return 0;
		}
		int v,i;
		for(v = head[u]; v != 0; v = next[v])
		{
			if (tot[v]==tot[u])
			{
				cal(depth+1,v);
				return 0;
			}
			else
				break;
		}

	 
		int cun=0;
		int ttt[70];
		int vv[70];
		__int64 tmp=0;
		for(v = head[u]; v != 0; v = next[v])
		{
			ttt[++cun]=tot[v];
			tmp+=tot[v];
			vv[cun]=v;
		}
//之前没把'\0'插入字典树,导致计算时要对两个相同字符串特殊处理(完全相同长度为n的2个串比较次数为2*(n+1))
		__int64 tmp2=0;
		for (i=1;i<=cun;i++)
		{
			tmp-=ttt[i];
			tmp2+=((__int64)ttt[i])*tmp;
		}
		tmp2*=(2*depth+1);
		ans+=tmp2; 
		for (i=1;i<=cun;i++)
		{
		
			if (tot[vv[i]]==1) 
				continue;
			cal(depth+1,vv[i]);
		}
	}
	
}; 


const int maxl = 1000 + 10;   // 每个单词最大长度

int n;
char  word[maxl];
Trie trie;

int main()
{
	int i; 
	int kase = 1; 
	while(scanf("%d", &n) == 1 && n) 
	{ 
		
		trie.clear();
		ok=0;
		for(  i = 1; i <=n; i++) 
		{
			scanf("%s", word);   
			trie.insert(word); 
		}
		ans=0; 
		
		trie.cal(0,0); 
		
		printf("Case %d: %lld\n", kase++, ans);
	}
	return 0;
}



不插入‘\0’:
// UVa11732 strcmp() Anyone?
// Yuhong Liu
#include<cstring>
#include<vector>
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxnode = 4005 * 1005 + 10; 
int  ok,ok2; 
long long       ans; // 答案
// 字母表为全体小写字母的Trie	
struct Trie 
{
	int head[maxnode]; // head[i]为第i个结点的左儿子编号
	int next[maxnode]; // next[i]为第i个结点的右兄弟编号
	
	char ch[maxnode];  // ch[i]为第i个结点上的字符
	int tot[maxnode];  // tot[i]为第i个结点为根的子树包含的叶结点总数
	int sz; // 结点总数
	
	void clear() { sz = 1; tot[0] = head[0] = next[0] = 0; } // 初始时只有一个根结点
	
	// 插入字符串s(包括最后的'\0'),沿途更新tot
	void insert(const char *s) 
	{
		int u = 0, v, n = strlen(s);
		tot[0]++;
		for(int i = 0; i < n; i++) 
		{
			// 找字符a[i]
			bool found = false;
			for(v = head[u]; v != 0; v = next[v])
			{
				if(ch[v] == s[i]) 
				{ // 找到了
					found = true;
					break;
				}
			}
			if(!found)
			{
				v = sz++; // 新建结点
				tot[v] = 0;
				ch[v] = s[i];
				next[v] = head[u];
				head[u] = v; // 插入到链表的首部
				head[v] = 0;
			}
			
			tot[v]++;
			u = v;
		} 
	} 
	
	
	// 统计
	long long    cal(int depth,int u) 
	{
		int v,i;

		for(v = head[u]; v != 0; v = next[v])
		{
			if (tot[v]==tot[u])
			{
				cal(depth+1,v);
				return 0;
			}
			else
				break;
		}


		int cun=0;
		int ttt[70];
		int vv[70];
		long long  tmp=0;
		for(v = head[u]; v != 0; v = next[v])
		{
			ttt[++cun]=tot[v];
			tmp+=tot[v];
			vv[cun]=v;
		}
			int temp=0;
		if (tmp<tot[u])
		{
			  temp=(tot[u]-tmp);  
			  //必须要把相同的合并在一起,否则导致run error,因为一种字符只能占一个位置
				  ttt[++cun]=temp;
				vv[cun]=-1; 
		}
		tmp=tot[u];
		long long  tmp2=0;
		for (i=1;i<=cun-1;i++)
		{
			tmp-=ttt[i];
			tmp2+=((long long )ttt[i])*tmp;
		}
		tmp2*=(2*depth+1);
		ans+=tmp2;
		if (temp)
		{ 
			ans+=((temp-1)*temp)/2*(2*depth+2);
		}
		for (i=1;i<=cun;i++)
		{

			if (vv[i]==-1)continue; 
			if (tot[vv[i]]==1) 
				continue;
			cal(depth+1,vv[i]);
		}
	}
	
}; 


const int maxl = 1000 + 10;   // 每个单词最大长度

int n;
char  word[maxl];
Trie trie;

int main()
{
	int i; 
	int kase = 1; 
	while(scanf("%d", &n) == 1 && n) 
	{ 
		
		trie.clear();
		ok=0;
		for(  i = 1; i <=n; i++) 
		{
			scanf("%s", word);   
			trie.insert(word); 
		}
		ans=0; 
		
		trie.cal(0,0); 
		
		printf("Case %d: %lld\n", kase++, ans);
	}
	return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值