【Ybt OJ】[字符串算法 第4章] 字典树Trie树

本文详细介绍了字典树(Trie树)在字符串算法中的应用,包括前缀统计、最大异或对、最长异或路径等例题的分析和解题思路。通过贪心策略证明了在某些问题中Trie树的优势,并提供了相关代码实现。

「 「 字符串算法 」 」 4 4 4章 字典树
目录:

A.前缀统计
B.最大异或对
C.最长异或路径
D.阅读理解

A . A. A. 例题 1 1 1 前缀统计

在这里插入图片描述

分析:

一道 T r i e Trie Trie模板题 需要支持插入 ( i n s e r t ) (insert) (insert)查询 ( q u e r y ) (query) (query)两个操作就好了

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#pragma GCC optimize(2)
#define reg register 
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
struct Trie{
	int son[305],end;
	char ch;
}trie[N];
int n,m,lenx,p,tot,ret;
char x[N];
void insert(char a[])  //往字典树加入一个字符串
{
	lenx=strlen(a);
	p=0;
	for(int i=0;i<lenx;i++)
	{
		if(!trie[p].son[x[i]])
		{
			trie[p].son[x[i]]=++tot;
			trie[p].ch=x[i];
		}
		p=trie[p].son[x[i]];
	}
	trie[p].end++;
}
int search(char a[])  //查询
{
	lenx=strlen(a);
	p=0;ret=0;
	for(int i=0;i<lenx;i++)
	{
		if(!trie[p].son[x[i]]) return ret;
		p=trie[p].son[x[i]];
		ret+=trie[p].end;
	}
	return ret;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",x);
		insert(x);
	}
	for(int i=1;i<=m;i++)
	{	
		scanf("%s",x);
		printf("%d\n",search(x));
	}
	return 0;
}

B . B. B. 例题 2 2 2 最大异或对

在这里插入图片描述

分析:

第一眼看可以暴力枚举 然后把优化开到极致还是会 T L E TLE TLE 1040 m s ( 1040ms( 1040ms(其实再卡卡就过了 ) ) )
这样看到 x o r xor xor 我们就可以想到 t r i e trie trie 然后要考虑怎么操作
我们先想想异或是干嘛的 相同为 0 0 0 不同才为 1 1 1
想让这个 x o r xor xor值最大 那么 1 1 1肯定要在最高位才是最大 也就是选的数高位尽可能不同
这时候就可以用 t r i e trie trie树了 让一个点有两个儿子 代表下一位是 0 0 0 1 1 1
再想想根节点 应该从最高位开始 因为越接近根节点优先级越高 那就是越高位
然后正常建树 查询就要从高位到低位 有不同的就选不同的 这样才是 1 1 1 不行就继续往后查就好

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+5;
struct Trie{
    int son[3],x;
} trie[32*N];
int n,a[N],ans,p,qwq[32],tot,k;
void insert()
{
    k=0;
    for(int i=31;i>=0;i--){  //根节点是最高位
        if (!trie[k].son[qwq[i]])
            trie[k].son[qwq[i]]=++tot;  //建树
        k=trie[k].son[qwq[i]];
    }
}
void find(){
    k=0;
    int ret=0;
    for(int i=31;i>=0;i--)
    {
        if(trie[k].son[qwq[i]^1]==0)  //有跟它这位相同
        {
            if(trie[k].son[qwq[i]])  //只有这位相同
                k=trie[k].son[qwq[i]];  //跳过往后查
            else
                break;  //说明后面都相同 就没有贡献了
        }else{  //这一位不同
            ret+=1<<i;  //加上贡献
            k=trie[k].son[qwq[i]^1];
        }
    }
    ans=max(ans,ret);
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        p=a[i];
        int t=0;
        memset(qwq,0,sizeof(qwq));
        while(p)
        {
            if(p&1)
                qwq[t++]=1;
            else
                qwq[t++]=0;
            p>>=1;
        }
        find();
        insert();
    }
    printf("%d",ans);
    return 0;
}

C . C. C. 例题 3 3 3 最长异或路径

洛谷 l i n k link link
在这里插入图片描述

分析:

h h h hhh hhh又是一道紫题
我们先把数变成二进制 存进 t r i e trie trie 节省空间
然后可以发现 一个数 x o r xor xor另一个数 如 a a a^ b b b 设我们得到 a a a ^ b = c b=c b=c 我们会发现 c c c ^ b b b又会 = a =a =a
也就是一个数 x o r xor xor两次同一个数 值不变
比如 7 7 7 ^ 4 4 4 = = = 0111 0111 0111 ^ 0100 0100 0100 = = = 0011 0011 0011 = = = 3 3 3
3 3 3 ^ 4 4 4 = = = 0011 0011 0011 ^ 0100 0100 0100 = = = 0111 0111 0111 = = = 7 7 7 也就是值不变
这样来 路径 [ x , y ] [x,y] [x,y]上的异或和 就可以变成 [ r o o t , x ] [root,x] [root,x]异或和 异或上 [ r o o t , y ] [root,y] [root,y]异或和
就是连续 x o r xor xor了两次 r o o t ( root( root(根节点 ) ) )
这样一来 后面我们做个贪心就可以了 就是如果这一位异或后是 1 ( 1( 1(不同 ) ) )沿着这条路径往下走 不然就原路往下走

贪心策略证明:

如果当前是 1 1 1 那么它的权值比它后面任何值的总和都大 所以我们直接选这一位就可以了
比如一个二进制数 10000000 10000000 10000000 无论如何 它也是比 01111111 01111111 01111111大的 ( ( (差不多是 最高位决定一切 的意思 ) ) )

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
//#pragma GCC optimize(2)
#define reg register 
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e6+3;
struct node{
	int u,v,next;
}edge[2*N];
int head[2*N],sum[2*N],n,tot,ans,tot2=-1;
struct Trie{
	int num[2];
}trie[2*N];
void add(int u,int v,int w)
{
	edge[++tot2]=(node){w,v,head[u]};
	head[u]=tot2;
}
void dfs(int dep,int fa)
{
	for(int i=head[dep];~i;i=edge[i].next)
	{
		int u=edge[i].u,v=edge[i].v;
		if(v==fa) continue;	
		sum[v]=sum[dep]^u;
		dfs(v,dep);
	}
}
void build(int val)
{
	int dep=0;
	for(int i=(1<<30);i;i>>=1)  //从最高位到最低位
	{
		bool place=val&i;  //取出这个数的位置
		if(!trie[dep].num[place]) trie[dep].num[place]=++tot;
		dep=trie[dep].num[place];
	}
}
int query(int x)
{
	int ret=0,dep=0;
	for(int i=(1<<30);i;i>>=1)
	{
		bool place=x&i;
		if(trie[dep].num[!place])  //如果可以异或
		{
			ret+=i;
			dep=trie[dep].num[!place];  //沿着走
		}
		else dep=trie[dep].num[place];  //不然就原路
	}
	return ret;
}
int main(){
	scanf("%d",&n);
	memset(head,-1,sizeof(head));
	for(int i=1;i<n;i++)
	{
		int x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		add(x,y,w);
		add(y,x,w);
	}
	dfs(1,-1);
	for(reg int i=1;i<=n;i++)
		build(sum[i]);
	for(reg int i=1;i<=n;i++)
		ans=max(ans,query(sum[i]));
	printf("%d",ans);
	return 0;
}

D . D. D. 例题 4 4 4 阅读理解

洛谷 l i n k link link
在这里插入图片描述
在这里插入图片描述

分析:

较为模板的 t r i e trie trie
要注意输出那里 注意换行的位置 然后就是存单词 t r i e trie trie树 然后给标记来查询就好了
但是这个数据比较恶心 你数组开了就全 M L E MLE MLE了 开了就剩最后一个点 W A WA WA
开的差不多大还会这样:
在这里插入图片描述
就很搞人心态 不过这仅限洛谷数据 h h h h hhhh hhhh 一本通 O J OJ OJ的数据还是很水的
然后把标记数组开个 b i t s e t bitset bitset就可以了 S T L STL STL果然优秀 节省 32 32 32倍空间 效率还更高

CODE:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#include<bitset>
//#pragma GCC optimize(2)
#define reg register 
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e4+10;
char s[1005];
struct Trie{
	int son[26];
	bitset<1011> end;  //hhhh
}trie[30*N];
int n,tot,m,lens;
void insert(char a[],int x)
{
	int now=0;
	for(int i=1;i<=lens;i++)
	{
		int p=a[i]-'a';
		if(!trie[now].son[p])
			trie[now].son[p]=++tot;  //存单词
		now=trie[now].son[p];
	}
	trie[now].end[x]=1;  //标记出现的单词
}
void solve(char a[])
{
	int now=0,f=1;
	for(int i=1;i<=lens;i++)
	{
		int p=a[i]-'a';
		if(!trie[now].son[p]){f=0;break;}
		now=trie[now].son[p];
	}
	if(f)
	for(int i=1;i<=n;i++)
		if(trie[now].end[i])  //有出现就输出
			printf("%d ",i); 
	printf("\n");
}
int main(){
	scanf("%d",&n);
	for(reg int i=1;i<=n;i++)
	{
		scanf("%d",&m);
		for(reg int j=1;j<=m;j++)
		{
			scanf("%s",s+1);
			lens=strlen(s+1);
			insert(s,i);
		}
	}
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%s",s+1);
		lens=strlen(s+1);
		solve(s);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值