数据结构·下 QWQ


KMP

时间复杂度 O(n)

在已知字符串中 寻找 子串


AcWing831. KMP字符串

//♥

#include <iostream>

using namespace std;

const int N = 1e5 + 5;
const int M = 1e6 + 5;

int n, m;
char p[N], s[M];
int ne[N];

int main()
{
    scanf("%d%s%d%s", &n, p+1, &m, s+1); // 让字符串从 1 开始存
    
    //计算 next
    for(int i = 2, j = 0; i <= n; i ++ )
    {
        while(j && p[j + 1] != p[i]) j = ne[j];
        if(p[j + 1] == p[i]) j ++ ;
        ne[i] = j;
    }
    
    for(int i = 1, j = 0; i <= m; i ++ )
    {
        while(j && p[j + 1] != s[i]) j = ne[j];
        if(p[j + 1] == s[i]) j ++ ;
        
        if(j == n) 
        {
            printf("%d ", i - n);
            j = ne[j]; // 好像也可以不用写这句
        }
    }
    
    return 0;
}


Trie

典型应用:高效的 统计,排序和保存 大量 字符串集合(不仅限于字符串

AcWing835. Trie字符串统计

//♥

#include <iostream>

using namespace std;

const int N = 1e5 + 5;

int n, cnt = 0;
char op;
string s;
int son[N][26], ct[N]; // son 用于存 孩子结点的下标, ct 用于存数量

void Insert(string s)
{
    int p = 0;
    
    for(int i = 0; i < s.size(); i ++ )
    {
        int u = s[i] - 'a';
        if(!son[p][u]) son[p][u] = ++ cnt; // 有 孩子 结点后,再启用内存
                                           // son 数组用于指引 孩子结点的 “p” 为多少
        p = son[p][u];
    }
    
    ct[p] ++ ;
}

int Query(string s)
{
    int p = 0;
    
    for(int i = 0; i < s.size(); i ++ )
    {
        int u = s[i] - 'a';
        if(!son[p][u]) return 0;
        p = son[p][u];
    }
    
    return ct[p];
}

int main()
{
    cin >> n;
    
    while(n --)
    {
        cin >> op >> s;
        
        if(op == 'I') Insert(s);
        else cout << Query(s) << endl;
    }
    
    return 0;
}

AcWing143. 最大异或对

//♥

#include <iostream>

using namespace std;

const int N = 1e5 + 5;
const int M = 31e5 + 5;

int n, cnt;
int son[M][2], a[N];

void Insert(int x)
{
    /*
        Q: 为什么 i 一定要从 30 到 0 ,不能反过来?
        
        A: 流程是 寻找这一位是否存在 “反数”,
           若存在则确定该位的值,继续寻找下一位
           
           若从 0 到 30,则是优先保证低位存在“反数”,可凑 1
           不一定能保证高位
           
           而从 30 到 0,是优先保证高位为 1
           先要保证高位尽可能大,才能找到最大值
           
           例如:
                要找 6(110)与  7(111)和 0(000),哪一队组合异或值更大
            若先确保低位,0 要找 1,会选中 7 (111) 分支,最后结果为 1
            若先确保高位,1 要找 0,会选中 0 (000) 分支,最后结果为 6(110)
    */
    int p = 0;
    for(int i = 30; i >= 0; i -- ) // i 表示右移的位数
    {
        int u = x >> i & 1; // 取 右移后 二进制最低位上的数字
        if(!son[p][u]) son[p][u] =  ++ cnt;
        p = son[p][u];
    }
}

int Query(int x)
{
    int res = 0, p = 0;
    
    for(int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if(son[p][!u])
        {
            res += 1 << i;
            p = son[p][!u];
        }
        else p = son[p][u];
    }
    
    return res;
}

int main()
{
    cin >> n;
    
    for(int i = 0; i < n; i ++ ) 
    {
        cin >> a[i];
        Insert(a[i]);
    }
    
    int ans = 0;
    for(int i = 0; i < n; i ++ ) ans = max(ans, Query(a[i]));
    
    cout << ans << endl;
    
    return 0;
}


并查集

  1. 将两个集合 合并
  2. 查询两个元素是否在一个集合中
//♥

#include <iostream>

using namespace std;

int const N = 1e5 + 5;

int n, m;
int p[N], r[N];

int Find(int x)
{
    if(p[x] != x)
    {
    	int px = Find(p[x]);
    	r[p[x]] = r[px]; // 根据题目需要 变通该式
    	p[x] = px;
    }
	return p[x]
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) p[i] = i;
    
    while(m --)
    {
        char op;
        int x, y;
        cin >> op >> x >> y;
        
        int px = Find(x), py = Find(y);
        if(op == 'M') 
        {
            if(px != py) p[px] = py;
        }
        else 
        {
            if(px == py) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}

典中典拓展域并查集

POJ.1182 食物链

// ♥

#include <iostream>

using namespace std;

const int N = 50010;

int n, k, ans = 0;
int p[3*N]; // [1, n]同类 [n+1, 2n]食物 [2n+1, 3n]天敌

int Find(int x)
{
    if(p[x] != x) p[x] = Find(p[x]);
    
    return p[x];
}

void Unite(int x, int y)
{
    int px = Find(x), py = Find(y);
    
    if(px != py) p[px] = py;
}

int main()
{
    cin >> n >> k;
    for(int i = 1; i <= 3*n; i ++ ) p[i] = i;
    
    while(k --)
    {
        int op, x, y;
        scanf("%d%d%d",&op,&x,&y);
        
        if(x > n || y > n)
        {
            ans ++ ;
            continue;
        }
        
        if(op == 1)
        {
            if(Find(x) == Find(y + n) || Find(x) == Find(y + 2*n)) ans ++ ;
            else
            {
                Unite(x, y);
                Unite(x + n, y + n);
                Unite(x + 2*n, y + 2*n);
            }
        }
        else
        {
            if(Find(x) == Find(y) || Find(x) == Find(y + n)) ans ++ ;
            else
            {
                Unite(x, y + 2*n);
                Unite(x + n, y);
                Unite(x + 2*n, y + n);
            }
        }
    }
    
    cout << ans << endl;
    
    return 0;
}

codeforces.D - Mahmoud and a Dictionary

// ♥

#include <iostream>
#include <unordered_map>

using namespace std;

const int N = 1e5 + 5;

int q, n, m, idx = 0;
int p[2*N]; // [1, n]同义 [n+1, 2*n]反义
unordered_map<string, int> mp;

int Find(int x)
{
    if(p[x] != x) p[x] = Find(p[x]);
    
    return p[x];
}

void Unite(int x, int y)
{
    int px = Find(x), py = Find(y);
    
    if(px != py) p[px] = py;
}

int main()
{
    cin >> n >> m >> q;
    for(int i = 1; i <= 2*n; i ++ ) p[i] = i;
    
    for(int i = 1; i <= n; i ++ )
    {
        string s;
        cin >> s;
        mp[s] = ++ idx;
    }
    
    string a, b;
    
    while(m --)
    {
        int op;
        scanf("%d", &op);
        cin >> a >> b;
        
        int x = mp[a], y = mp[b];
        if(op == 1)
        {
            if(Find(x) != Find(y + n))
            {
                Unite(x, y);
                Unite(x + n, y + n);
                printf("YES\n");
            }
            else printf("NO\n");
        }
        else
        {
            if(Find(x) != Find(y))
            {
                Unite(x, y + n);
                Unite(x + n, y);
                printf("YES\n");
            }
            else printf("NO\n");
        }
        
    }
    
    while(q --)
    {
        cin >> a >> b;
        
        int x = mp[a], y = mp[b];
        
        if(Find(x) == Find(y)) printf("1\n");
        else if(Find(x) == Find(y + n)) printf("2\n");
        else printf("3\n");
    }
    
    return 0;
}

顺便试了试 输出 哪个快 (好像不明显)
在这里插入图片描述


堆 (Heap)

//♥

#include <iostream>

using namespace std;

const int N = 1e5 + 5;

int n, m;
int h[N], cnt = 0;

void down(int i)
{
    int t = i;
    if(i*2 <= cnt && h[i<<1] < h[t]) t = i << 1;
    if(i*2 + 1 <= cnt && h[i<<1|1] < h[t]) t = i << 1 | 1;
    
    if(t != i)
    {
        swap(h[i], h[t]);
        down(t); // ☆向下更新
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i ++ ) cin >> h[++cnt];
    
    for(int i = n / 2; i > 0; i --) down(i);
    
    while(m --)
    {
        cout << h[1] << ' ';
        swap(h[1], h[cnt]);
        cnt -- ;
        down(1);
    }
    
    return 0;
}

AcWing839. 模拟堆

//♥

#include <iostream>

using namespace std;

const int N = 1e5 + 5;

int n;
int h[N], idx = 0; // idx 有 序号、下标 的意思
int k[N], pk[N], cnt = 0; // k 存储第 k 个数下标,ph 存储 h[i] 的cnt,即 k 数组下标
                          // 即 k[i] = j; pk[j] = i;

void heap_swap(int i, int j)
{
    swap(h[i], h[j]);
    swap(k[pk[i]], k[pk[j]]); // 先通过 pk 寻找对应的 k 的下标,交换 k
    swap(pk[i], pk[j]);       // 再交换 pk 指引对应 k 的下标
}

void up(int i)
{
    if((i>>1) > 0 && h[i] < h[i>>1]) // 判断 上方是否存在结点 ,0 默认为空的结点
    {
        heap_swap(i, i>>1);
        up(i>>1); // 继续向上递归
    }
}

void down(int i)
{
    int t = i;
    
    if((i << 1) <= idx && h[t] > h[i<<1]) t = i << 1;
    if((i << 1|1) <= idx && h[t] > h[i<<1|1]) t = i << 1|1;
    
    if(t != i)
    {
        heap_swap(i, t);
        down(t);
    }
}

int main()
{
    cin >> n;
    
    while(n --)
    {
        string op;
        cin >> op;
 
        if(op == "I")
        {
            cin >> h[ ++ idx];
            k[ ++ cnt] = idx; // 记录 第 cnt 个输入的数 对应 h 数组中的下标
            pk[idx] = cnt; // 记录 h[idx] 对应 k 数组中的下标
            up(idx);
        }
        else if(op == "PM") cout << h[1] << endl;
        else if(op == "DM")
        {
            heap_swap(1, idx);
            -- idx;
            down(1);
        }
        else if(op == "D")
        {
            int kk;
            cin >> kk;
            
            kk = k[kk]; // 查询 第 kk 个数 在 h 数组中的下标
            heap_swap(kk, idx);
            -- idx;
            up(kk);
            down(kk);
        }
        else
        {
            int kk, x;
            cin >> kk >> x;
            
            kk = k[kk];
            h[kk] = x;
            up(kk);
            down(kk);
        }
        
    }
    
    return 0;
}


哈希表

当数据范围很大,而数据数量较小时使用
例如 -109 ≤ ai ≤ 109, 1 ≤ N ≤ 105;

关于 0x3f3f3f3f 到底有多大(第一个是数字 0)
3f3f3f3f > 109
在这里插入图片描述
两个相加不会超过 int
在这里插入图片描述
在这里插入图片描述


开放寻址法

//♥

#include <iostream>
#include <cstring>

using namespace std;

#define INF 0x3f3f3f3f

const int N = 2e5 + 3; // 尽量开 2 ~ 3 倍 减少冲突事件,利用空间节省时间
					   //  + 3 ,质数 也是为了减少冲突事件

int n;
int h[N];

int Find(int x)
{
    int t = (x % N + N) % N; // (x % N + N) 防止出现负数
    while(h[t] != INF && h[t] != x)
    {
        t ++ ;
        if(t == N) t = 0;
    }
    
    return t;
}

int main()
{
    memset(h, 0x3f, sizeof(h)); // 0x3f3f3f3f > 1e9, 是范围外的数
    							// 可以当做 判断 h[i] 是否为空的标记
    cin >> n;
    while(n --)
    {
        char op;
        int x;
        cin >> op >> x;
        
        if(op == 'I') h[Find(x)] = x;
        else
        {
            if(h[Find(x)] != INF) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}


拉链法

// ♥

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e5 + 3;

int n, idx = 0;
int e[N], ne[N], h[N]; // e 存储元素,ne 存储 上一个取模结果相等元素 的下标,
                       // h[i] 存储最新 取模结果为 i 的元素 在 e 中的下标

void Insert(int x)
{
    int t = (x % N + N) % N; // (x % N + N) 防止出现负数
    e[idx] = x;
    ne[idx] = h[t];
    h[t] = idx ++ ;
}

int Find(int x)
{
    int t = (x % N + N) % N;
    for(int i = h[t]; i != -1; i = ne[i])
    {
        if(e[i] == x) return 1;
    }
    
    return 0;
}

int main()
{
    cin >> n;
    
    memset(h, -1, sizeof(h));
    while(n --)
    {
        string op;
        int x;
        cin >> op >> x;
        
        if(op == "I") Insert(x);
        else 
        {
            if(Find(x)) puts("Yes");
            else puts("No");
        }
    }
    
    return 0;
}

字符串哈希

看做一个 P 进制的 数值
P 一般取 131 或 13331

在这里插入图片描述

AcWing.841. 字符串哈希

//♥

#include <iostream>

using namespace std;

typedef unsigned long long ULL; // 用 unsigned 可不用取模
                                // 如数据溢出,等价于对 2^64 取模
const int N = 1e5 + 5;
const int P = 131; // 通常取 131 或 13331

int n, m;
char str[N];
ULL h[N], p[N]; // p[i] 表示 p^i 的值

ULL Find(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    cin >> n >> m >>  str + 1; // +1 从下标 1 开始存储
    
    p[0] = 1; // 不初始化 p 数组中的值会一直为 0 
    for(int i = 1; i <= n; i ++ )
    {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + str[i];
    }
    
    while(m --)
    {
        int l1, l2, r1, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        
        if(Find(l1, r1) == Find(l2, r2)) puts("Yes");
        else puts("No");
        
    }
    
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值