Trie字典树


Trie 是一种用于实现字符串快速检索的多叉树结构。Trie的每个结点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符c,就沿着当前节点的c字符指针,走向该指针的结点。
一篇清晰好懂的博文: Trie树(字典树,单词查找树)

字典树适用于处理一些字符串问题(某些时候跟哈希的用处有点像),还有一些异或问题。


P2580 于是他错误的点名开始了

先把名单上的所有名字存放到字典树里,每念一个名字查找一下,字典树末端指针指向的结点中的num用于记录点到这里的次数,这样每次到结尾时根据num可以判断是否重复,如果不能顺利走到尾的话说明念错了。

#include<bits/stdc++.h>
using namespace std;

struct Node
{
	int son[30];
	int num;
}node[500010];
int n,m,now;

void insert(string s)
{
	int p=0,len=s.size();
	for(int i=0;i<len;i++)
	{
		if(node[p].son[s[i]-'a']==0)
			node[p].son[s[i]-'a']=++now;
		p=node[p].son[s[i]-'a'];
	}
}

int find(string s) 
{
	int p=0,len=s.size();
	for(int i=0;i<len;i++)
	{
		if(node[p].son[s[i]-'a']==0)
			return 0;
		p=node[p].son[s[i]-'a'];
	}
	node[p].num++;
	return node[p].num;
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		string str;
		cin>>str;
		insert(str);
	}
	cin>>m;
	for(int i=0;i<m;i++)
	{
		string name;
		cin>>name;
		int ans=find(name);
		//cout<<"ans:"<<ans<<endl;
		if(ans==1) printf("OK\n");
		if(ans==0) printf("WRONG\n");
		if(ans>=2) printf("REPEAT\n");
	}
    return 0;
}

P5149 会议座位

看题目的描述很容易联想到当时学的归并排序求逆序对数,但是这里并不是一些有大小顺序的数而是很多字符串,怎么办呢?

f ( s t r i n g ) − > n u m b e r f(string)->number f(string)>number

我们可以采取很多种映射的办法,最直接的是map,其他的一些比如哈希等,这里采用字典树,插入时走到字符串末尾时给每个字符串一个编号,重排之后原有的顺序被打乱,归并排序的时候顺便求一下逆序对数就好啦。

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+10;
int n,tot,trie[N][100],a[N],b[N],temp[N];
long long ans;
void insert(string s,int x)
{
	int p=0,len=s.size();
	for(int i=0;i<len;i++)
	{
		if(!trie[p][s[i]-'A'])
			trie[p][s[i]-'A']=++tot;
		p=trie[p][s[i]-'A'];
	}
	a[p]=x;
}

void query(string s,int x)
{
	int p=0,len=s.size();
	for(int i=0;i<len;i++)
		p=trie[p][s[i]-'A'];
	b[x]=a[p];
}

void merge_sort(int l,int r)
{
	if(l==r) return;
	int mid=(l+r)>>1;
	merge_sort(l,mid);
	merge_sort(mid+1,r);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	{
		if(b[i]<=b[j]) temp[k++]=b[i++];
		else temp[k++]=b[j++],ans+=mid-i+1;
	}
	while(i<=mid) temp[k++]=b[i++];
    while(j<=r) temp[k++]=b[j++];
    for(int p=l;p<=r;p++) b[p]=temp[p];
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		string name;
		cin>>name;
		insert(name,i);
	}
	for(int i=1;i<=n;i++)
	{
		string name;
		cin>>name;
		query(name,i);
	}
	//for(int i=1;i<=n;i++)
		//cout<<b[i]<<' ';
	merge_sort(1,n);
    cout<<ans;
    return 0;
}


以上两题是在字典树中插入字符串。如果要解决异或问题的话,实际上是把二进制数看作01串插到字典树中,靠近树梢的一般是较高位,它们权重比较大,如果位高权重的说话了,靠近树梢的就可以闭嘴了>_<(bushi)


143. 最大异或对

暴力的做法是对于每一个数,枚举其他所有数,求出(n-1)个异或值,有了字典树之后可以快速找到跟当前数异或值最大的那个。把这些01串都插入字典树,然后枚举这n个数,在这n个数对应找出的异或值中选最大的就行了。
怎么找到一个数对应的最大异或值呢?从高位(树根)开始每次都进入反子树(比如101011,第一次进入0子树,第二次进入1子树,第三次进入0子树,以此类推),如果想要进入的子树为空的话就迫不得已进入同子树。这样可以尽可能地选取到异或值大的数。每向下移一层,异或值乘2,如果进入反子树异或值加1。

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int n,trie[N*32][2],tot,a[N];
int maxn=0;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void insert(int x)
{
	int p=0;
	for(int i=31;i>=0;i--)
	{
		int t=(x>>i)&1;
		if(!trie[p][t])
			trie[p][t]=++tot;
		p=trie[p][t];
	}
}

int query(int x)
{
	int q=0,ans=0,t;
	for(int i=31;i>=0;i--)
	{
		t=(x>>i)&1;//取出当前位
		if(!trie[q][!t])//反子树为空
		{
			q=trie[q][t];
			ans<<=1;
		}
		else
		{
			q=trie[q][!t];//进入反子树
			ans<<=1;
			ans+=1;//0^1=1
		}
	} 
	return ans;
}
int main()
{
	n=read();
	for(int i=0;i<n;i++)
	{
		a[i]=read();
		insert(a[i]);
	}
	for(int i=0;i<n;i++)
	{
		int temp=query(a[i]);
		maxn=max(maxn,temp);
	}
	cout<<maxn;
    return 0;
}

P4551 最长异或路径

设a[x]表示根结点到x的路径上所有边权的异或值

a [ x ] = a [ f a t h e r ( x ) ] a[x]=a[father(x)] a[x]=a[father(x)] xor w e i g h t ( x , f a t h e r ( x ) ) weight(x,father(x)) weight(x,father(x))

可以先DFS求出所有的a[x],则任意两个结点x,y路径上所有边权的异或值就等于a[x]^a[y],问题转化成从a[1]~a[n]这n个数中选两个,求最大异或值,同上一道题。

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+10;
int n,nex[N*2],head[N*2],val[N*2],a[N],to[N*2],k;
int trie[N<<4][2],tot;
int maxn=0;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void add(int x,int y,int w)
{
	to[++k]=y;
	nex[k]=head[x];
	head[x]=k;
	val[k]=w;	
}

void dfs(int x,int fa)
{
	for(int i=head[x];i;i=nex[i])
	{
		int y=to[i];
		if(y==fa) continue;
		a[y]=(val[i]^a[x]);
		dfs(y,x);
	}
}

void insert(int x)
{
	int p=0;
	for(int i=31;i>=0;i--)
	{
		int t=(x>>i)&1;
		if(!trie[p][t])
			trie[p][t]=++tot;
		p=trie[p][t];
	}
}

int query(int x)
{
	int q=0,ans=0,t;
	for(int i=31;i>=0;i--)
	{
		t=(x>>i)&1;
		if(!trie[q][!t])
		{
			q=trie[q][t];
			ans<<=1;
		}
		else
		{
			q=trie[q][!t];
			ans<<=1;
			ans+=1;
		}
	} 
	return ans;
}

int main()
{
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read(),w=read();
		add(x,y,w);
		add(y,x,w);
	}
	a[1]=0;
	dfs(1,0);
	for(int i=1;i<=n;i++)
		insert(a[i]);
	for(int i=1;i<=n;i++)
	{
		int temp=query(a[i]);
		maxn=max(maxn,temp);
	}
	cout<<maxn;
    return 0;
}



P6824 「EZEC-4」可乐

聪明值不会超过 2 21 2^{21} 221,我们在此范围内枚举每一个数x,在字典树中进行查找,求出x^k的每个二进制位,枚举每一位时,反子树内的结点个数可以直接被统计上(最后必然符合条件),一直枚举到最后一位(注意结尾要特判一下,因为是<=k)。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;

int n,k,a[N],now,ans,sz[N*21],trie[N*21][2],tot;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void insert(int x)
{
    int p=0;
    for(int k=21,c;k>=0;k--)
	{
        c=(x>>k)&1;
        if(!trie[p][c]) trie[p][c]=++tot;
        p=trie[p][c];
        sz[p]++;//记录有多少个数以到目前为止的这一串“01”为前缀 
    }
}

int query(int x)
{
    int ret=0,p=0;
    bool flag=0;
    for(int i=21,c;i>=0;i--)
	{
        c=(x>>i)&1;//x这一位的值
        int t=(k>>i)&1;//k这一位的值
        if(t==1) ret+=sz[trie[p][1-c^t]];//如果k这一位的值是1,那么这一位取1-(c^t)的时候一定可行,反之一定不可行
        if(!trie[p][c^t]) {flag=1;break;}//如果走不到就返回
        p=trie[p][c^t];
    }
    if(!flag) ret+=sz[p];//走到最底层侧了,这里对应的数就是k 
    return ret;
}
int main()
{
	n=read(),k=read();
	for(int i=0;i<n;i++)
	{
		a[i]=read();
		insert(a[i]);
	}
	int temp;
	for(int i=0;i<(1<<21);i++)
	{
        ans=max(ans,query(i));
        if(ans==n) break;
    }
	cout<<ans;
    return 0;
}


一只题单:Trie
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春弦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值