acwing算法基础课II

acwing基础课 II 数据结构

链表

数组模拟单链表

单链表 格式就是这样吧 e[N] 代表当前点 ne[N] 代表下一代的点. 插入也很简洁.

Code
int ne[N6], idx = 1, e[N6];

void insert(int x, int y) {
    ne[idx] = ne[x];
    ne[x] = idx;
    e[idx++] = y;
}

void insert_head(int x) {
    insert(0, x);
}

void delete_node(int x) {
    ne[x] = ne[ne[x]];
}

int main() {
    int m = read();
    ne[0] = -1;
    char s;
    while (m--) {
        cin >> s;
        int x = read(), y;
        if (s == 'H') insert_head(x);
        else if (s == 'I') y = read(), insert(x, y);
        else if (s == 'D') delete_node(x);
    }
    for (int i = ne[0]; i != -1; i = ne[i]) {
        O(e[i]);
    }
}
数组模拟双链表 ★★★
Code
int idx = 2, e[N5], l[N5], r[N5];
void init() {
    l[1] = 0, r[0] = 1; //这个l0 和 r1真能用到么
}
void insert(int k, int x) {
    e[idx] = x, l[idx] = k, r[idx] = r[k];
    l[r[k]] = idx, r[k] = idx++;
}

void insertHead(int x) {
    insert(0, x);
}
void insertTail(int x) {
    insert(l[1], x);
}
void deleteNode(int x) {
    l[r[x]] = l[x], r[l[x]] = r[x];
}
int main() {
    int m = read();
    char op[10];
    init();

    while (m--) {
        cin >> op; // 这里以后开大一点吧...
        int x = read(), y = 0;
        if (op[0] == 'R') insertTail(x);
        else if (op[0] == 'L') insertHead(x);
        else if (op[0] == 'D') deleteNode(x + 1);
        else if (op[1] == 'R') y = read(), insert(x + 1, y);
        else y = read(), insert(l[x + 1], y);
    }
    for (int i = r[0]; i != 1; i = r[i]) O(e[i]);
    return 0;
}

栈 & 队列 & 单调..

模拟栈

雪菜有的是 从 tt=0 不知道有没有影响.

然后empty() 是 tt>0

注意 stk, 和 tt = -1; ???

Code
int stk[N5], tt = -1; //记住下标是从 -1 开始的
void push(int x) {
    stk[++tt] = x;
}
void pop() {
    --tt;
}
int query() {
    return stk[tt];
}
bool empty() {
    return tt == -1;
}
模拟队列

尾进 头出. tt=-1, hh = 0

Code
int q[N5], tt = -1, hh;
void push(int x) {
    q[++tt] = x;
}
void pop() {
    ++hh;
}
int query() {
    return q[hh];
}
bool empty() {
    return hh > tt;
}
单调栈

输出每个数左边第一个比他小的数. => 单增的栈.

Code
int stk[N6], tt = 0;
int main() {
    int n = read();
    rep(i, 0, n) {
        int x = read();
        while (tt && x <= stk[tt]) tt--;
        O(tt ? stk[tt] : -1);
        stk[++tt] = x;
    }
    return 0;
}
单调队列(双端)
滑动窗口最大值和最小值.
Code
int a[N6], q[N6], tt = -1, hh;
int main() {
    int n = read(), k = read();
    rep(i, 0, n) a[i] = read();
    rep(i, 0, n) {
        if (hh <= tt && i - k + 1 > q[hh]) hh++; // 如果左边不对, 出栈.
        while (hh <= tt && a[q[tt]] >= a[i]) tt--; //最小值, 单增区间
        q[++tt] = i;
        if (i - k + 1 >= 0) O(a[q[hh]]);
    }
    puts("");
    tt = -1, hh = 0;
    rep(i, 0, n) {
        if (hh <= tt && i - k + 1 > q[hh]) hh++;
        while (hh <= tt && a[q[tt]] <= a[i]) tt--; //最大值, 单减区间
        q[++tt] = i;
        if (i - k + 1 >= 0) O(a[q[hh]]);
    }
    return 0;
}
栈. 表达式求值 ★★★★

不会做... 不过这个顺序..

Code
int stk[N6], tt = 0, top = 0;
char op[N6];

void eval() {
    int y = stk[tt--], x = stk[tt--];
    char c = op[top--];
    if (c == '*') x = x * y;
    else if (c == '/') x = x / y;
    else if (c == '-') x = x - y;
    else if (c == '+') x = x + y;
    stk[++tt] = x;
}
int main() {
    unordered_map<char, int> um = { {'+', 1}, {'-', 1},
        {'*',2},{'/',2} }; // 定义 priority, 小的进栈前大的先完毕
    string equ;
    cin >> equ;
    rep(i, 0, equ.size()) {
        char c = equ[i];
        if (isdigit(c)) {
            int x = 0, j = i;
            while (j < equ.size() && isdigit(equ[j])) {
                x = x * 10 + equ[j++] - '0';
            }
            i = j - 1; // 因为后面要 i ++;
            stk[++tt] = x;
        }
        else {
            if (c == '(') op[++top] = c; // 左括号直接进栈.
            else if (c == ')') {
                while (op[top] != '(') eval();
                //右括号碰到左前一直运算.
                --top;
            }
            else {
                while (top && op[top] != '(' &&
                    um[c] <= um[op[top]]) eval();
                //相同情况就可以eval了
                // 为什么换成 < 不行呢? 
                    // 24 - 5 + 3; 这种 -号需要一定先运算
                op[++top] = c;
            }
        }
    }
    while (top) eval();
    O(stk[tt]);
    return 0;
}

KMP ★★★★

其实也不难 不过像个dp么? 或者正确性挺难证明出来的.

模板字符串用 p, 带匹配字符串用 s

Code
int n, m;
int ne[N5];
char s[N6], p[N5];

int main() {
    cin >> n >> p + 1 >> m >> s + 1; 
    for (int i = 2, j = 0; i <= n; i++) {
        while (j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j++; //求next 就是子串和自己匹配.
        ne[i] = j;
    }
    da(ne, n + 2);
    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++;
        if (j == n) {
            O(i - n); j = ne[j];
        }
    }
    return 0;
}

数组元素的目标和

双指针

Code
int a[N5], b[N5];

int main() {
    int n = read(), m = read(), x = read();
    rep(i, 0, n) a[i] = read();
    rep(i, 0, m) b[i] = read();
    int r = m - 1;
    rep(l, 0, n) {
        while (r >= 0 && a[l] + b[r] > x) r--;
        if (a[l] + b[r] == x) {
            O(l), O(r);
            return 0;
        }
    }
    return 0;
}

判断子序列

双指针

Code
int a[N5], b[N5];

int main() {
    int m = read(), n = read();
    int j = 0;
    rep(i, 0, m) b[i] = read();
    rep(i, 0, n) a[i] = read();
    rep(i, 0, n) {
        if (j < m && b[j] == a[i]) j++;
    }
    puts(j == m ? "Yes" : "No");
    return 0;
}

Tire树

Tire 字符串统计

快速 存储字符串集合的数据结构

树用 son 存储, 个数用 cnt 存

Code
int son[N5][26], cnt[N5], idx; //idx为0的那个是根 空串 "";
char str[N5];
void insert(char* str) {
    int p = 0;
    for (int i = 0; str[i]; i++) {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
    cnt[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;
        p = son[p][u];
    }
    return cnt[p];
}
int main() {
    int n = read();
    while (n--) {
        char op[2];
        scanf("%s%s", op, str);
        if (*op == 'I') insert(str);
        else printf("%d\n", query(str));
    }
    return 0;
}
最大异或对.
Code
int a[N5], idx;
int son[N6 * 3][2]; //这里为什么需要 N6*3 个?
void insert(int x) {
    int p = 0;
    per(i, 31, 0) {
        int& s = son[p][x >> i & 1];
        if (!s) s = ++idx;
        p = s;
    }
}
int search(int x) {
    int p = 0, res = 0;
    per(i, 31, 0) {
        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 = read();
    rep(i, 0, n)a[i] = read(), insert(a[i]);
    int res = 0;
    rep(i, 0, n) {
        res = max(res, search(a[i]));
    }
    O(res);
    return 0;
}

并查集

合并集合
Code
int p[N5];
// int find(int x) {
//     int& p = h[x]; // 这样写的话 一定要写引用啊...
//     if (p != x) p = find(p);
//     return p;
// }
int find(int k) {
    if (p[k] != k) p[k] = find(p[k]); // 记一下模板..
    return p[k];
}
int main() {
    int n = read(), m = read();
    rep(i, 1, n + 1) {
        p[i] = i;
    }
    rep(i, 0, m) {
        char op[2]; scanf("%s", op);
        int l = read(), r = read();
        if (op[0] == 'M') p[find(l)] = find(r);
        else
            puts(find(l) == find(r) ? "Yes" : "No");
    }
    return 0;
}
837. 连通块中点的数量
Code
int p[N5], cnt[N5];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
void merge(int a, int b) {
    int c = find(a), d = find(b);
    if (c == d) return;
    if (cnt[c] > cnt[d]) swap(c, d);
    p[c] = d, cnt[d] += cnt[c];
}
int main() {
    int n = read(), m = read();
    rep(i, 0, n + 1) p[i] = i, cnt[i] = 1;
    while (m--) {
        char op[5];
        scanf("%s", op);
        int a = read(), b;
        if (op[0] == 'C') b = read(), merge(a, b);
        else if (op[1] == '1') b = read(),
            puts(find(a) == find(b) ? "Yes" : "No");
        else if (op[1] == '2') printf("%d\n", cnt[find(a)]);
    }
    return 0;
}
食物链 ★★★

注意一下 c++ 里面 -2 % 3 == -2 ........

注意一下 怎么算这个链条上的东西.... 并查集 好东西.

Code
int p[N5], num[N5];
int find(int x) {
    if (p[x] != x) {
        int t = find(p[x]);
        num[x] += num[p[x]];
        // 相当于一个剪枝的过程, 可是这种递归剪枝好难写啊
        // num[x] %= 3;
        p[x] = t;
    }
    return p[x];
}

int main() {
    int n = read(), m = read();
    rep(i, 0, n + 1) p[i] = i;
    int ans = 0;
    rep(i, 0, m) {
        int a = read(), b = read(), c = read();
        if (b > n || c > n) { ans++; continue; }
        int pb = find(b), pc = find(c);
        if (a == 1) {
            if (pb != pc) {
                num[pb] = num[c] - num[b], p[pb] = pc;
            }
            else {
                if ((num[c] - num[b]) % 3)ans++;
            }
        }
        else {
            if (pb == pc) {
                if ((num[b] - num[c] - 1) % 3) ans++;
            }
            else {
                num[pb] = num[c] + 1 - num[b], p[pb] = pc;
            }
        }
        // printf("%d %d %d %d", a, b, c, ans); puts("");
    }
    O(ans);
    return 0;
}

如何手写一个堆?

  1. 插入一个数 a[++idx] =x, up(idx);
  2. 求集合当中的最小值 a[1];
  3. 删除最小值. a[1] = a[idx--]; down(1);
  4. 删除任意一个元素a[k] = a[idx--], down(k), up(k);
  5. 修改任意一个元素. a[k] = x, down(k), up(k);

基本结构: 完全二叉树.

小根堆: 每个点都是小于等于左右儿子的.

堆 的 下标是从1开始的. +2 还是 +1 的问题. 不是大问题.

O(n) 的建堆方式.

手写堆排序: ★★
Code
int a[N5], n, m;
void down(int u) {
    int t = u;
    if (u * 2 <= n && a[u * 2] < a[t]) t = u * 2;
    if (u * 2 + 1 <= n && a[u * 2 + 1] < a[t]) t = u * 2 + 1;
    if (u != t) {
        swap(a[u], a[t]);
        down(t);
    }
}

int main() {
    n = read(), m = read();
    rep(i, 1, n + 1) {
        a[i] = read();
    }
    per(i, n / 2 + 1, 1) down(i);
    rep(i, 0, m) {
        O(a[1]), a[1] = a[n--], down(1);
    }
    return 0;
}
模拟堆 ..... 好恶心的板子啊. ★★★
Code
int h[N5], hp[N5], ph[N5], n, m, idx; // p 是下标, h 是堆的位置.
void heap_swap(int u, int v) {
    swap(h[u], h[v]);
    swap(ph[hp[u]], ph[hp[v]]);
    swap(hp[u], hp[v]);
}
void down(int u) {
    int t = u;
    if (u * 2 <= idx && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= idx && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t) {
        heap_swap(u, t);
        down(t);
    }
}
void up(int u) {
    while (u / 2 && h[u / 2] > h[u]) {
        heap_swap(u / 2, u);
        u /= 2;
    }
}
void insert(int u) {
    h[++idx] = u;
    ph[++m] = idx, hp[idx] = m;
    up(idx), down(idx);
}
void deleteNode(int u) {
    u = ph[u];
    if (u == idx) idx--;
    else heap_swap(idx--, u), down(u), up(u);
}
int main() {
    n = read();
    rep(i, 0, n) {
        char op[5];
        int x, y;
        scanf("%s", op);
        if (!strcmp(op, "I")) { insert(read()); }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM")) { deleteNode(hp[1]); }
        else if (!strcmp(op, "D")) { deleteNode(read()); }
        else if (!strcmp(op, "C")) {
            x = read(), y = read();
            h[ph[x]] = y, down(ph[x]), up(ph[x]);
        }
        else {
            cout << op;
            puts(" error");
        }

    }
    return 0;
}

哈希

存储结构.

哈希函数的映射: 109105

  1. 冲突的处理?
  2. 删除的话, 开个 bool 数组, 打记号就行.
  3. N 应该取成质数, 而且离 2 的 整数次幂尽可能的远.

拉链法. 0 -> 1051

开放寻址法
Code
const int N = 2e5 + 3, null = 0x3f3f3f3f; // 用不在范围内的数代表空.
int h[N];
int find(int x) { // 注意一下这个 find 的写法.
    int k = (x % N + N) % N;
    while (h[k] != null && h[k] != x) {
        k++;
        if (k == N) k = 0; // 回到开头.
    }
    return k;
}
int main() {
    memset(h, 0x3f, sizeof h); // memset 是按照字节来进行 memset 的.
    int n = read();
    while (n--) {
        char op[5];
        int x;
        scanf("%s%d", op, &x);
        int k = find(x);
        if (op[0] == 'I') h[k] = x;
        else puts(h[k] == x ? "Yes" : "No");
    }
    return 0;
}
拉链法
Code
const int N = 100003;
int h[N], e[N], ne[N], idx;

void insert(int x) {
    int p = (x % N + N) % N; // 取模这里是个坑....
    e[idx] = x, ne[idx] = h[p], h[p] = idx++;
}
bool query(int x) {
    int k = (x % N + N) % N;
    for (int i = h[k];i != -1;i = ne[i])
        if (e[i] == x) return true;
    return false;
}
int main() {
    memset(h, -1, sizeof h);
    int n = read();
    char op[5];
    while (n--) {
        int x;
        scanf("%s%d", op, &x);
        if (*op == 'I') insert(x);
        else puts(query(x) ? "Yes" : "No");
    }
    return 0;
}
字符串哈希的方式 ★★★
字符串前缀哈希法.

h[N] 里面是 前缀的哈希值, 把 字符串看成是 p进制的哈希数.

把每一个字符串变成数字. 最后进行取模.

  1. 每一个字符不能映射成 0. 尽量从1 开始.
  2. 假定人品足够好, 不存在冲突. p=131 / 13331 Q = 2 ** 64 时候. 假定没有冲突
  3. 也需要预处理出来 p[N] p 的 N次方.
  4. 可以利用前缀哈希, 计算所有子串的哈希?
    1. 如果 [l, k] 可以计算, h[k] - h[l] * p^(k - l + 1)
Code
int P = 131;
char s[N5];
ULL h[N5], p[N5];

ULL get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1]; // 这里有个边界.
}

int main() {
    int n = read(), m = read();
    scanf("%s", s + 1);
    p[0] = 1;
    rep(i, 1, n + 1) {
        p[i] = p[i - 1] * P;
        h[i] = h[i - 1] * P + s[i];
    }
    while (m--) {
        int l1 = read(), r1 = read(), l2 = read(), r2 = read();

        puts((r2 - l2 == r1 - l1) &&
            (get(l1, r1) == get(l2, r2)) ? "Yes" : "No");
    }
    return 0;
}

STL

容器.

Code
vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算,按字典序

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

stack, 栈
    size()
    empty()
    push()  向栈顶插入一个元素
    top()  返回栈顶元素
    pop()  弹出栈顶元素

deque, 双端队列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)

    set/multiset
        insert()  插入一个数
        find()  查找一个数
        count()  返回某一个数的个数
        erase()
            (1) 输入是一个数x,删除所有x   O(k + logn)
            (2) 输入一个迭代器,删除这个迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大于等于x的最小的数的迭代器
            upper_bound(x)  返回大于x的最小的数的迭代器
    map/multimap
        insert()  插入的数是一个pair
        erase()  输入的参数是pair或者迭代器
        find()
        []  注意multimap不支持此操作。 时间复杂度是 O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
    和上面类似,增删改查的时间复杂度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  返回有多少个1

    any()  判断是否至少有一个1
    none()  判断是否全为0

    set()  把所有位置成1
    set(k, v)  将第k位变成v
    reset()  把所有位变成0
    flip()  等价于~
    flip(k) 把第k位取反

作者:yxc
链接:https://www.acwing.com/blog/content/404/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Code

Code

Code

posted @   benenzhu  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示
主题色彩