Trie字典树到01字典树到最小异或生成树(更新中)

刷题:
字典树题目
01字典树题目


一、字典树(单词,公共前缀)

字典树利用公共前缀的方法用空间换取时间,常用来换map,在大多数情况下也比hash算法快,下面模板

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
int a[maxn][26],tot,n,m,f[maxn],sum[maxn],rt;
char sz[maxn];
void insert(char *s){
    
    
	int rt=0;
	for(int i=0;s[i];i++){
    
    
		char x=s[i]-'a';
		if(!a[rt][x])	a[rt][x]=++tot;
		sum[a[rt][x]]++;
		rt=a[rt][x];
	}
	f[rt]=1;
}
//查询整个单词
bool find(char *s){
    
    
	rt=0;
	for(int i=0;s[i];i++){
    
    
		int x=s[i]-'a';
		if(!a[rt][x])	return 0;
		rt=a[rt][x];
	}
	return f[rt];
	//return 1;	如果仅仅查询前缀 
}
//查询前缀出现次数 
int search(char *s){
    
    
	rt=0;
	for(int i=0;s[i];i++){
    
    
		int x=s[i]-'a';
		if(!a[rt][x])	return 0;
		rt=a[rt][x];
	}
	return sum[rt];
}
int main(){
    
    
	cin>>n;
	for(int i=1;i<=n;i++){
    
    
		cin>>sz;
		insert(sz);
	}
	cin>>m;
	for(int i=1;i<=m;i++){
    
    
		cin>>sz;
		if(find(sz))	cout<<"Yes"<<endl;
		else	cout<<"No"<<endl;
	}
	cin>>m;
	for(int i=1;i<=m;i++){
    
    
		cin>>sz;
		cout<<search(sz)<<endl;
	}
}

字典树当然也有删除操作啦,只不过一般数据是数字,常用于01字典树中,因为字母什么的26倍或更大,数据比较大(当然也可以根据下文同理)

一些模板题就不说了,下面给几个简单的问题:
1、一堆字符串,找出一个字符串(不一定是其中的),使该字符串作为前缀的次数∗该字符串的长度结果最大。(LightOJ1224)
(存入所有节点之后再重新扫一遍Trie树复杂度会高很多,可以在插入字符串的时候进行统计)

2、一堆木棍左右两端涂有颜色,相同颜色的可以连接在一起,问所有木棍能否都连上(POJ2513)–>无向图判欧拉路

3、问你某个单词是否可以拆成单词表中的其他两个单词。 HDU1247
(建两颗Trie树,然后分别正序倒序插入每个单词,对每个单词查询的时候,我们分别正序倒序查询,对出现过单词的前缀下标进行标记,对每个出现过单词的后缀进行标记,最后扫描标记数组,如果某个位置前缀后缀均被标记过,则表示可以拆成单词表中的两个其他单词。)

4、这题就有点意思:POJ2408
给定若干个字符串,将其分组,按照组成元素相同为一组,输出数量最多的前5组,数量相同的输出字典序较小的一组,每组按照字典序输出所有字符串,不输出重复的字符串。

方法一:(hash+排序:将所有的字符串统计字符后hash,排序之后确定每组的个数并且确定一组中字典序最小的字符串。根据个数以及字符串对组进行排序。)

方法二:(字典树+set:如果两个单词可以通过重新排列组合变成相同单词,那么他们的字典序最小的排列方式一定是相同的,所以我们可以利用每个元素的最小排列方式判定是否在同一个集合,字典树在这里用于判定某个字符串时候出现过。最后用set来保存以便维持字典序)


二、01字典树(异或最值)

01字典树是一棵最多32层 (一般pos设31也可以) 的二叉树,其每个节点的两条边分别表示二进制的某一位的值为 0 还是为 1. 将某个路径上边的值连起来就得到一个二进制串。

注意:一般要先插入一个0

模板题:CF706D(删除操作+最大异或对)—>要删除的话,原来的bool f[maxn*32]改成int 类
大意:有一个整数集合A,A刚开始只有0,现在有m(m<=200000)个操作,操作分为询问和修改操作。对于修改操作,分为添加和删除一个数(<=10^9),数据保证删除的数一定是A中存在的。对于询问操作,给出一个数x,要求找出集合A中,与x异或后最大的那个数。

const int maxn=2e5+7;
int tot,rt,a[32*maxn][2];
int f[32*maxn]; 
void insert(int x,int w){
    
    
    rt=0;
    for(int i=31;i>=0;i--){
    
    
        int k=(x>>i)&1;
        if(!a[rt][k])	a[rt][k]=++tot;
        rt=a[rt][k];
        f[rt]+=w;
    }
} 
int search(int x){
    
    
    rt=0;
    int ans=0;
    for(int i=31;i>=0;i--){
    
    
        int k=(x>>i)&1;
        if(f[a[rt][k^1]]){
    
    
        	rt=a[rt][k^1];
        	ans|=1<<i;
		}
        else rt=a[rt][k];
    }
    return ans;
}
int n,m;
char sz[2];
int main(){
    
    
	insert(0,1);
	scanf("%d",&m);
	while(m--){
    
    
		scanf("%s%d",sz,&n);
		if(sz[0]=='+')		insert(n,1);
		else if(sz[0]=='-')	insert(n,-1);
		else	printf("%d\n",search(n));
	}
}

/*
10
+ 8
+ 9
+ 11
+ 6
+ 1
? 3
- 8
? 3
? 8
? 11


11
10
14
13

*/

一些有点意思的题:

1、给你一个序列,求子序列区间(连续的)异或起来的最大值和最小值(Lightoj1269)

三步骤:先插入一个零—>预处理出前缀异或和—>边插入前缀异或和时,边用当前值在字典树中查找最值,更新
一注意:应该先用前缀异或和查找,再插入这值,防止自身影响异或最小值一定为0.

2、给你n个数,让你在n个数中选三个,使得(a1+a2)^a3的值最大,a1!=a2!=a3(下标不等于)hdu5536

二步骤:n个数建01字典树—>两次for循环得(ai+aj),在树中找最值。
一注意:得(ai+aj)后,应暂时删除ai和aj,防止与自身产生最值,查找完后再重新加入ai和aj。

延伸:一般这种题考难的话,1、会把数据放到图里,结合图论考。2、结合其他数论考

1、01字典树+图的遍历 ---------最长异或路径 POJ 3764

大意:给你一棵树,n个节点,n-1条边每条边i都有一个权值wi。定义任意两点间的权值为:这两点间的路径上的所有边的值的异或。比如a点和b点间有i,j,k三条边,那么ab两点间的权值为:wi ^ wj ^ wk。求这个最大的权值(最长异或路径)

因为a ^ b==a ^ c ^ b ^ c 。所以 x 到 y 的异或值 == x 到根节点的异或值 ^ y 到根节点的异或值。
二步骤:dfs来生成根到每一个点的异或路径 —> 插入这些值—>01字典树遍历(暴力两层for循环遍历不如字典树的O(n*log2n))

2、 01字典树+dp -----------BZOJ 4260

给出 n 个数,求两个不相交的区间中的元素异或后的和的最大值

思路有点类似求两个不相交的区间和的和的最大值。

三步骤:求异或的前后缀d1[maxn],d2[maxn] —> dp[i] 表示前 i 个数中任意区间异或后的最大值,那么dp[i] = max(dp[i-1], search(d1[i])),然后把 d2[i] 插入到 01字典树中,后缀反之 —> 遍历答案sum=max(sum,dp[i]+fp[i+1])。

二注意:求完dp[maxn],tot和01字典树要重置,还要先插入一个0.

3、还有一道难题:http://codeforces.com/problemset/problem/1055/F
找出树上所有路径异或和的第K大
这题我代码没打,不知道思路对不对,应该在上面的基础上要用到大小根堆,不知道内存会不会不够。


三、最小异或生成树

猜你喜欢

转载自blog.csdn.net/weixin_45606191/article/details/107736338