Trie树
我们暂时只讨论字符串中只有小写字母的情况(即26种情况),我们的Trie树可以将当下所有的字符串一股脑地存入一个二维数组中,这个就是Trie树:son[N][26]
Trie树本质上是一颗多叉树,对于字母而言最多有26个子结点,对于son[p][u],我们的理解如下:
p为当前结点的大致位置
u表示的是当前的字母,比如'a'代表0,'z'代表25
son[p][u]=++idx中的idx表示的是下一个结点的位置。
为了标记一个字符串出现的个数,我们会在字符串结束的位置对这个字符串出现的次数进行记录。
即cnt[p]++
#include<iostream>
using namespace std;
const int N = 1e5+10;
int son[N][26],idx;
int cnt[N];
char s[N];
void insert(char q[])
{
int p=0;
for(int i=0;q[i];i++)
{
int u=q[i]-'a';
if(!son[p][u])son[p][u]=++idx;//一定要注意这里是++idx
p=son[p][u];
}
cnt[p]++;
}
int query(char q[])
{
int p=0;
for(int i=0;q[i];i++)
{
int u=q[i]-'a';
if(!son[p][u])return 0;
p=son[p][u];
}
return cnt[p];
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
char op[2];
scanf("%s%s",op,s);
if(op[0]=='I')
{
insert(s);
}
else
{
cout<<query(s)<<endl;
}
}
return 0;
}
经典习题:Trie字符串统计
并查集
并查集是对集合进行分块的数据结构,对于每一个结点,1~n,在最开始各为一个集合
我们用一个数组来维护集合,p[i]=i表示i这个结点的祖宗结点为i,随着各个结点的加入,集合会越来越庞大,树的结构也会越来越复杂,那么我们寻找祖宗结点的时间也会增加。
由于路径上所有结点的祖宗结点相同,所以我们在寻找的时候就可以对路径进行压缩:
int find(int x){ //返回x的祖先节点 + 路径压缩
//祖先节点的父节点是自己本身
if(p[x] != x){
//将x的父亲置为x父亲的祖先节点,实现路径的压缩
p[x] = find(p[x]);
}
return p[x];
}
如图为全过程:
具体的代码实现如下:
#include<iostream>
using namespace std;
const int N = 1e5+10;
int p[N];
int find(int x)
{
if(p[x]!=x)return p[x]=find(p[x]);
return p[x];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)p[i]=i;
while(m--)
{
char op[2];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0]=='M')
{
a=find(a),b=find(b);
p[a]=b;
}
else
{
if(find(a)==find(b))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
}
return 0;
}
经典习题: 合并集合