字典树
题目:835. Trie字符串统计 - AcWing题库
又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
我们用int sun[N][26]来表示树种某个节点,假如单词hello是第一个插入的单词,那么
son[0]['h'-'a']=1;
son[1]['e'-'a']=2;
son[2][''i'-'a']=3;
son[3]['i'-'a']=4;
son[4]['o'-'i']=5;
son[0]['w'-'a']=6。就这样依次类推。
int cnt[N]来表示某节点有多少次出现。比如插入两次"hello",而'o'这个节点是5号节点,所以cnt[5]=2。
idx 表示当前使用到哪个节点了。
#include<iostream>
#include<string>
using namespace std;
const int N =100008;
int son[N][26],cnt[N],idx;
char str[N];void insert(char s[]){int p=0;for(int i=0;s[i];i++){int u=s[i]-'a';if(!son[p][u]) son[p][u]=++idx;p = son[p][u];}cnt[p]++;
}int query(char s[]){int p=0;for(int i=0;s[i];i++){int u = s[i]-'a';if(! son[p][u]) return 0;p = son[p][u];}return cnt[p];
}int main(){int n;scanf("%d",&n);for(int i=0;i<n;i++){char s[2];//指定长度为2scanf("%s%s",s,str);if(s[0]=='I'){insert(str);}else{int out = query(str);printf("%d\n",out);}}return 0;
}
并查集
题目一
836. 合并集合 - AcWing题库
一共有 n个数,编号是 1∼𝑛,最开始每个数各自在一个集合中。
现在要进行 m𝑚 个操作,操作共有两种:
M a b
,将编号为 𝑎 和 𝑏 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;Q a b
,询问编号为 𝑎 和 𝑏 的两个数是否在同一个集合中;
输入格式
第一行输入整数 𝑛 和 𝑚。
接下来 m𝑚 行,每行包含一个操作指令,指令为 M a b
或 Q a b
中的一种。
输出格式
对于每个询问指令 Q a b
,都要输出一个结果,如果 a𝑎 和 b𝑏 在同一集合内,则输出 Yes
,否则输出 No
。
每个结果占一行。
数据范围
1≤n,m≤1e5,1≤𝑛,𝑚≤1e5
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
- 在计算机科学中,并查集是一种树形的数据结构,用于处理不交集的合并(union)及查询(find)问题。
- 并查集主要是用来解决动态连通性问题的,比如查询网状图中两个节点的状态,进行数学中集合相关的操作, 如求两个集合的并集等。
其主要操作:
- findRoot:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一个子集
- unionElements:将两个子集合并成同一个集合
基本原理:
每一个集合用一棵树表示。树根的编号就是集合的编号,每个节点存储它的父节点,p[x]表示x的父节点。注意:只有根节点存在等式p[x]==x,其余节点均是p[x] != x。
问题:
一:如何判断树根?
答:if(p[x]==x)
二:如何求x的集合编号?
答:while(p[x] != x) x = p[x]
即依次向上找
三:如何合并两个集合?
答:如果a、b不是同一个集合,那么p[find_ancestors(a)] = find_ancestors(b);//合并,让集合a的父节点是集合b的父节点。
代码
#include<iostream>
using namespace std;const int N =100009;
int p[N],n,m;int find_ancestors(int x){//查找节点x的祖先节点+路径压缩(递归实现)if(p[x] != x) p[x] = find_ancestors(p[x]); //只要节点的父节点不是本身(不是根节点),就递归调用让其父节点等于其父节点的祖先return p[x];// // // // 也可以用循环来做// int org =x;// while(p[x] != x){// x = p[x];// }// while(p[org] != org){// p[org] = x;// org = p[org];// }// return x;
}int main(){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'){p[find_ancestors(a)] = find_ancestors(b);//合并,让集合a的父节点是集合b的父节点}else{if(find_ancestors(p[a]) == find_ancestors(p[b])){printf("Yes\n");}else{printf("No\n");}}}return 0;
}
题目二
给定一个包含 𝑛 个点(编号为 1∼𝑛)的无向图,初始时图中没有边。
现在要进行 𝑚 个操作,操作共有三种:
C a b
,在点 𝑎 和点 𝑏 之间连一条边,𝑎 和 𝑏 可能相等;Q1 a b
,询问点 𝑎 和点 𝑏 是否在同一个连通块中,𝑎 和 𝑏 可能相等;Q2 a
,询问点 𝑎 所在连通块中点的数量;
输入格式
第一行输入整数 𝑛 和 𝑚。
接下来 𝑚 行,每行包含一个操作指令,指令为 C a b
,Q1 a b
或 Q2 a
中的一种。
输出格式
对于每个询问指令 Q1 a b
,如果 a𝑎 和 b𝑏 在同一个连通块中,则输出 Yes
,否则输出 No
。
对于每个询问指令 Q2 a
,输出一个整数表示点 a𝑎 所在连通块中点的数量
每个结果占一行。
数据范围
1≤n,m≤1e5,1≤𝑛,𝑚≤1e5
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
思路
每个连通块可以看成一个集合,开始时每个节点各自是一个集合,当两个点之间连一条边时相当于将两个集合合并。因此也可以使用并查集模板,对于查询一个连通块中节点的数量,可以在初始化时定义一个size[]数组,其只对根节点有意义,当合并时将两个连通块的size[]相加,因为起始从1开始,所以每次合并都会成功正确更新(这也是一个思想,计算个数,如果整个集合从1开始合并,最后合并一个更大的集合,那么可以用这种思想,相当于从底向上推导。)
代码
#include<iostream>
using namespace std;
const int N = 1e5+10;
int p[N],mysize[N];int find_ancestors(int x)
{if(p[x] != x) p[x] = find_ancestors(p[x]);return p[x];//避坑,这里时返回p[x],而不是x,因为x是节点编号,p[x]才是其祖宗节点的编号
}int main(){int n,m;int a,b;char str[5];scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){p[i] = i;mysize[i] =1;}while(m--){scanf("%s",str);if(str[0]=='C'){scanf("%d%d",&a,&b);int pa,pb;pa = find_ancestors(a);pb = find_ancestors(b);if(pa==pb) continue;else{mysize[pa] += mysize[pb];p[pb] = pa;}}else{if(str[1]=='1'){scanf("%d%d",&a,&b);int pa,pb;pa = find_ancestors(a);pb = find_ancestors(b);if(pa==pb){printf("Yes\n");}else{printf("No\n");}}else{scanf("%d",&a);printf("%d\n",mysize[find_ancestors(a)]);}}}return 0;
}