AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)

数据结构

  • 一、单链表⭐
  • 二、双链表⭐
  • 三、栈
    • ①模拟栈⭐
    • ②表达式求值 (还没写)
  • 四、模拟队列
  • 五、单调栈⭐
  • 六、单调队列(滑动窗口)⭐
  • 七、KMP字符串⭐⭐
  • 八、Trie
    • ①Trie字符串统计⭐
    • ②最大异或对⭐
  • 九、并查集
    • ①合并集合
    • ②食物链
  • 十、堆
    • ①堆排序
    • ②模拟堆(还没写)
  • 十一、哈希表
    • ①模拟散列表
    • ②字符串哈希

一、单链表⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第1张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第2张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第3张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第4张图片

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第5张图片

时间复杂度
插入删除节点0(1)
查询节点0(n)

代码

#include 
#include 
#include 

using namespace std;

const int N = 100010;
int head, e[N], ne[N], idx;
//head记录头节点下标
//e[N]记录每个节点的val值
//ne[N]记录每个节点的下一个点的下标
//idx记录当前待用节点的下标

void init()
{
    head = -1; //初始状态头节点指向-1
    idx = 0; //当前待用空间下标为0,表示链表为空
}

//将x插入到头节点后面
void addHead(int x)
{
    e[idx] = x;
    ne[idx] = head;
    head = idx ++;
}

//将x插入到下标为k的节点后
void add(int k, int x)
{
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx ++;
}

//删除节点k的后面一个点
void remove(int k)
{
    ne[k] = ne[ne[k]]; 
}

int main()
{
    int m;
    cin >> m;
    
    init(); //记得要初始化
    while (m --)
    {
        string op;
        cin >> op;
        
        if (op == "I") //将x插入到下标为k - 1的节点后面
        {
            int k, x;
            cin >> k >> x;
            add(k - 1, x);
        }
        else if (op == "D")
        {
            int k;
            cin >> k;
            if (k) remove(k - 1);
            else head = ne[head]; //删除头节点
        }
        else 
        {
            int x;
            cin >> x;
            addHead(x); //头插法
        }
    }
    
    for (int i = head; i != -1; i = ne[i]) //遍历单链表
    {
        cout << e[i] << " ";
    }
    return 0;
}

二、双链表⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第6张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第7张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第8张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第9张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第10张图片

代码

#include 

using namespace std;

const int N = 100010;

int head, tail, e[N], l[N], r[N], idx;

//初始化
void init()
{
    head = 0,tail = 1;
    r[0] = 1,l[1] = 0;
    idx = 2;
}

//在k后插入k
void insert(int k, int x)
{
    e[idx] = x;
    l[idx] = k, r[idx] = r[k]; //idx为x的下标
    l[r[k]] = idx, r[k] = idx;
    idx ++;
}

//移除点k
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main()
{
    int m;
    cin >> m;
    init();
    
    while (m --)
    {
        string op;
        cin >> op;
        
        int k, x;
        if (op == "L")
        {
            cin >> x;
            insert(0, x);
        }
        else if (op == "R")
        {
            cin >> x;
            insert(l[1], x);
        }
        else if (op == "D")
        {
            cin >> k;
            remove(k + 1);
        }
        else if(op == "IL")
        {
            cin >> k >> x;
            insert(l[k + 1], x);
        }
        else
        {
            cin >> k >> x;
            insert(k + 1, x);
        }
    }
    
    for (int i = r[0]; i != 1; i = r[i]) //从头节点的右侧开始遍历
    {
        cout << e[i] << " ";
    }
    return 0;
}

三、栈

①模拟栈⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第11张图片

代码

#include 

using namespace std;
const int N = 100010;

int stack[N], tt;

int main()
{
    int m;
    cin >> m;
    
    while (m --)
    {
        string op;
        cin >> op;
        int x;
        
        if (op == "push")
        {
            cin >> x;
            stack[ ++ tt] = x;
        }
        else if (op == "pop") tt --;
        else if (op == "query") cout << stack[tt] << endl;
        else 
        {
            if (tt > 0) cout << "NO" << endl;
            else cout << "YES" << endl;
        }
    }
    return 0;
}

②表达式求值 (还没写)

算法

代码


四、模拟队列

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第12张图片

代码

#include 

using namespace std;

const int N = 100010;
int q[N], hh, tt = -1;

int main()
{
    int m;
    cin >> m;
    
    while (m --)
    {
        string op;
        cin >> op;
        int x;
        
        if (op == "push")
        {
            cin >> x;
            q[ ++ tt] = x; //将x从队尾插入
        }
        else if (op == "pop") hh ++; //弹出队头元素
        else if (op == "query") cout << q[hh] << endl; //查询队头元素
        else 
        {
            if (hh <= tt) cout << "NO" << endl; //队列非空
            else cout << "YES" << endl;
        }
        
    }
    return 0;
}

五、单调栈⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第13张图片

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第14张图片

代码

#include 
#include 

using namespace std;

const int N = 100010;

stack <int> s;

int main()
{
    int n;
    cin >> n;
    
    for (int i = 0; i < n; i ++)
    {
        int x;
        cin >> x;
        
        if (s.empty()) cout << "-1 "; //如果栈中为空,表示左边没有比x小的数
        else
        {
            while (s.size() && s.top() >= x) s.pop();
            if (s.empty())cout << "-1 ";
            else cout << s.top() << " ";
        }
        s.push(x); //x一定会入栈
    }
    return 0;
}

六、单调队列(滑动窗口)⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第15张图片

代码

#include 

using namespace std;

const int N = 1000010;

int a[N], q[N]; //a[N]用于存储数据, q[N]是一个单调队列,存储窗口中单调元素的下标
int hh = 0, tt = -1; //队列首尾指针初始化

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);
    
    for(int i = 0; i < n; i ++)
    {
        scanf("%d", &a[i]); //读入a[N]原始数据
    }
    
//-------------------------------输出最小值
    for(int i = 0; i < n; i ++) //i是遍历a[N]的指针
    {
        if(i - k + 1 > q[hh]) hh ++; //当窗口元素数大于k时,队首元素应出队
        
        while(hh <= tt && a[q[tt]] >= a[i]) tt --;//当队列不为空且队尾元素比当前要入队的元素大时,队尾元素出队,使得队中元素单调递增
        q[ ++ tt] = i;//当前元素入队
        
        if(i >= k - 1) printf("%d ", a[q[hh]]);//当窗口元素具有k个数时,输出队首元素下标对应的值
    }
    printf("\n");
    
//-------------------------------输出最大值

    hh = 0, tt = -1; //记得同样操作再做一遍时,记得初始化hh和tt
    for(int i = 0; i < n; i ++) 
    {
        if(i - k + 1 > q[hh]) hh ++; //当窗口元素数大于k时,队首元素应出队
        
        while(hh <= tt && a[q[tt]] <= a[i]) tt --;//当队列不为空且队尾元素比当前要入队的元素小时,队尾元素出队,使得队中元素单调递减
        q[ ++ tt] = i;
        
        if(i >= k - 1) printf("%d ", a[q[hh]]);
    }
        
    return 0;
}

七、KMP字符串⭐⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第16张图片
代码

#include 

using namespace std;

const int N = 1000010;
int n, m;
char p[N], s[N];
int ne[N];

int main()
{
    //字符数组可以直接cin整个串,可以从下标1开始输入,string不可以从下标1开始输入
    cin >> n >> p + 1 >> m >> s + 1;
    
    //填充ne数组,当i等于1时不匹配,只能回退到0,所以ne[1] = 0, 所以i 从2开始
    for (int i = 2, j = 0; i <= n; i ++)
    {
        while (j && p[i] != p[j + 1]) j = ne[j]; //当不匹配时,j回退到最长相同前缀的末尾
        if (p[i] == p[j + 1]) j ++; //如果能够匹配,j向后移动
        ne[i] = j; //以i结尾的子串后缀最长能够匹配上子串的前j个字符
    }
    
    //遍历整个主串,依次输出匹配成功的起始位置
    for (int i = 1, j = 0; i <= m; i ++)
    {
        while (j && s[i] != p[j + 1]) j = ne[j]; //当子串与主串匹配不成功,子串后退到最长前缀的位置
        if (s[i] == p[j + 1]) j ++; //如果能够匹配,j向后移动
        if (j == n) //匹配成功,i - n 是起始位置
        {
            cout << i - n << " ";
            j = ne[j]; //将j退回最长前缀的位置,继续后续的匹配
        }
    }
    return 0;
    
}

八、Trie

①Trie字符串统计⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第17张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第18张图片

代码

#include 

using namespace std;

const int N = 100010;
int son[N][26], cnt[N], idx;
//son记录trie数,cnt记录每个词出现的次数,idx记录每个字符所占用的下标

//加入字符串
void add(char str[])
{
    //idx = 0既表示根节点也表示空节点
    int p = 0;
    for (int i = 0; str[i]; i ++) //字符串数字末尾为0
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx; //当前字符不存在,增加
        p = son[p][u]; //向下继续遍历
    }
    cnt[p] ++; //p指向字符串最后一个字符,计数++
}

int query(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++)
    {
        int u = str[i] - 'a';
        if (!son[p][u]) return 0; //若没找到,直接返回0
        p = son[p][u]; //向下继续遍历
    }
    return cnt[p]; //p指向字符串最后一个字符
}

int main()
{
    int n;
    cin >> n;
    
    while (n --)
    {
        char op;
        char x[N];
        cin >> op >> x;
        if (op == 'I') add(x);
        else cout << query(x) << endl;
    }
    return 0;
}

②最大异或对⭐

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第19张图片

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第20张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第21张图片

代码

#include 

using namespace std;

const int N = 100010, M = 4000000;

int son[M][2], a[N], idx;

void add(int x) //将x的31位二进制插入trie树
{
    int p = 0;
    for (int i = 30; ~i; i --) //i >= 0,当i等于-1时,二进制全为1,取反则为0
    {
        int &s = son[p][x >> i & 1]; //准备走的路
        if (!s) s = ++ idx; //如果没有路,就自己创一个
        p = s; //继续往下走
    }
}

int query(int x)
{
    int p = 0, res = 0;
    for (int i = 30; ~i; i --) 
    {
        int s = x >> i & 1;
        if (son[p][!s]) //另一个岔路是否走得通
        {
            res += 1 << i; //结果
            p = son[p][!s];
        }
        else p = son[p][s]; //走不通就走走同一条路
    }
    return res;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> a[i], add(a[i]);
    
    int res = 0;
    for (int i = 0; i < n; i ++) res = max(res, query(a[i]));
    
    cout << res;
    return 0;
}

九、并查集

①合并集合

算法

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第22张图片

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第23张图片

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第24张图片 AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第25张图片

代码

#include 

using namespace std;

const int N = 100010;

int n, m;
int p[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x]; //返回父节点p[x],而不是x,根节点处x = p[x],但是递归调用,后面的节点x != p[x]
}
int main()
{
    scanf("%d%d", &n, &m);
    
    for (int i = 0; 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') //op[0]
        {
            p[find(a)] = find(b); //将a集合纳入b集合,a的父亲节点为b的根节点
            //在合并时可以加入cnt数组记录集合中点的数量
            //cnt[find(b)] += cnt[find(a)];
        }
        else
        {
            if (find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

②食物链

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第26张图片

代码

#include 

using namespace std;

const int N = 50010;

int p[N], d[N];

int find(int x)
{
    if (p[x] != x)
    {
        int t = find(p[x]);
        d[x] += d[p[x]]; //d[i]存储的是i结点到它父节点的距离,d[x]就是等于x到p[x]的距离
        p[x] = t; //先更新距离,再更新父节点
    }
    return p[x];
}

int main()
{
    int n, k;
    cin >> n >> k;
    
    for (int i = 1; i <= n; i ++) p[i] = i;
    
    int res = 0;
    while (k --)
    {
        int op, a, b;
        cin >> op >> a >> b;
        
        if (a > n || b > n) res ++;
        else
        {
            int pa = find(a), pb = find(b);
            if (op == 1)
            {
                //a和b在同一个集合中,但是a的类型和b的类型不相同(根据a和b到根节点的距离判断)
                //判断a和b模三是否相等,不要写成a % 3 != b % 3,这样写存在正负号的问题
                if (pa == pb && (d[a] - d[b]) % 3) res ++;
                else if (pa != pb)
                {
                    p[pa] = pb;
                    d[pa] = d[b] - d[a]; //使得(d[a] + d[pa] - d[b]) % 3 == 0
                }
            }
            else
            {
                //a和b在同一个集合中,但是a吃b(根据a和b到根节点的距离判断)
                if (pa == pb && (d[a] - d[b] - 1) % 3) res ++;
                else if (pa != pb)
                {
                    p[pa] = pb;
                    d[pa] = d[b] - d[a] + 1; //使得(d[a] + d[pa] - d[b] - 1) % 3 == 0
                }
            }
        }
    }
    cout << res;
    return 0;
}

十、堆

①堆排序

算法

AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第27张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第28张图片
初始建堆,若采用插入的方法,则时间复杂度为0(nlogn)
优化方法:将所有元素先乱序存入数组(建立起一个乱序完全二叉树),然后对这棵树的前n/2个元素进行一遍down操作,即可构成堆
该优化方法由下图的证明可以得出时间复杂度为0(n)
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第29张图片

代码

#include 

using namespace std;

const int N = 100010;

int h[N], cnt; //堆的大小不要定义成size,会报错

void down(int u)
{
    int t = u; //使用t记录三个值中最小值的编号
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        swap(h[u], h[t]);
        down(t);
    }
}

int main()
{
    int n, m;
    cin >> n >> m;
    
    //构建完全二叉树
    for (int i = 1; i <= n; i ++) cin >> h[i];
    cnt = n;
    
    //0(n)建堆
    for (int i = n / 2; i; i --) down(i);
    
    //输出堆顶,并删掉堆顶
    while (m --)
    {
        cout << h[1] << " ";
        h[1] = h[cnt];
        cnt --;
        down(1);
    }
    return 0;
}

②模拟堆(还没写)

算法

代码


十一、哈希表

①模拟散列表

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第30张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第31张图片
并且取模的这个数需要是质数,并且尽可能离二的整次幂尽可能的远,这样发生冲突的概率是最小的
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第32张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第33张图片
这两种方法的删除操作都是开一个数组标记,不会真正进行删除
总结如下
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第34张图片

代码

********拉链法********
#include 
#include 

using namespace std;

const int N = 100003;

int h[N], e[N], ne[N], idx;
int n;

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof(h));
    
    while (n --)
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        
        int k = (x % N + N) % N; //找到x应该在的位置
        
        if (op[0] == 'I') //将x插入对应的链表,头插法
        {
            e[idx] = x;
            ne[idx] = h[k];
            h[k] = idx ++;
        }
        else //找到对应链表,遍历该链表,查看x是否存在
        {
            bool flag = false;
            for (int i = h[k]; ~i; i = ne[i])
            {
                if (e[i] == x)
                {
                    flag = true;
                    break;
                }
            }
            if (flag) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

******开放寻址法******
#include 
#include 

using namespace std;

const int N = 200003, null = 0x3f3f3f3f; //开放寻址法通常是开到元素个数的两到三倍

int h[N];
int n;

int find(int x)
{
    int k = (x % N + N) % N;
    while (h[k] != x && h[k] != null) //当前坑位有人并且不是x
    {
        k ++;
        if (k == N) k = 0; //如果已经遍历到最后一个坑位了,就从0号坑从头开始找
    }
    return k; //如果x存在,则返回x的位置,如果x不存在,则返回x应该在的位置
}
int main()
{
    scanf("%d", &n);
    memset(h, 0x3f, sizeof(h));
    
    while (n --)
    {
        char op[2];
        int x;
        scanf("%s%d", op, &x);
        
        int k = find(x); //找到x应该在的位置
        
        if (op[0] == 'I') h[k] = x;
        else //
        {
            if (h[k] != null) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

②字符串哈希

算法
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第35张图片
AcWing算法学习笔记:数据结构(单链表 + 双链表 + 栈 + 队列 +单调栈 + 单调队列 + KMP + Trie + 并查集 + 堆 + 哈希表)_第36张图片

代码

#include 

using namespace std;

typedef unsigned long long ULL; //没有等号,要加分号,相当于自动取模2^64

const int N = 100010, P = 131; //或者P可以取13331
int n, m;
ULL h[N], p[N];
char str[N];


//计算L~R这段字符串的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1]; //多项式对齐
}

int main()
{
    scanf("%d%d%s", &n, &m, str + 1); //str从1开始,所以使用char数组存储
    
    p[0] = 1; //p^0
    for (int i = 1; i <= n; i ++)
    {
        p[i] = p[i - 1] * P; //因为涉及i - 1,所以i从1开始
        h[i] = h[i - 1] * P + str[i]; //第一位字符哈希值就是字符本身,往后*P + 字符
        //str[i]的值是多少没有关系,只要不为0且不相同就行
    }
    
    while (m --)
    {
        int l1, r1, l2, r2;
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        if (get(l1, r1) != get(l2, r2)) puts("No");
        else puts("Yes");
    }
    return 0;
}

你可能感兴趣的:(算法,数据结构,学习)