字典树、并查集适用于算法竞赛

字典树

题目: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];//指定长度为2
        scanf("%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𝑚 个操作,操作共有两种:

  1. M a b,将编号为 𝑎 和 𝑏 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. 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∼𝑛)的无向图,初始时图中没有边。

现在要进行 𝑚 个操作,操作共有三种:

  1. C a b,在点 𝑎 和点 𝑏 之间连一条边,𝑎 和 𝑏 可能相等;
  2. Q1 a b,询问点 𝑎 和点 𝑏 是否在同一个连通块中,𝑎 和 𝑏 可能相等;
  3. Q2 a,询问点 𝑎 所在连通块中点的数量;
输入格式

第一行输入整数 𝑛 和 𝑚。

接下来 𝑚 行,每行包含一个操作指令,指令为 C a bQ1 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;
}

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

背水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值