2025.5.8-5.9 学习随记(数据结构)

数据结构大多都学过了,跳的这么快 hh。
主要也没学啥,就扫描线、可持久化 trie、主席树和 treap。


1.线段树 -- 扫描线(复习)

大概就是按照横坐标排序,然后对于每个矩形左边设权

为 1,右边为 -1,然后用线段树维护从坐标上每段区

间的情况,每个节点应该表示离散化后相邻两个数之间

的情况,应该是视题目而定的。

亚特兰蒂斯(扫描线)

是以线段作为节点的,所以感觉不 pushdown 就挺理所应当的呀。
然后思路就是对于每两个横坐标的直线来看,维护之间有被覆盖的 y 区间即可。
用线段树,对于每个矩形左加右减,比较好维护,就是细节有点多。

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
struct Segment_Tree
{
    int l, r;
    int cnt;
    double len;
} tr[N * 4];
struct Quest
{
    double x, y1, y2;
    int w;
    
    bool operator < (const Quest &W) const
    {
        return x < W.x;
    }
} q[N];
vector<double> lsh;

int find(double x)
{
    return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if (l < r)
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
    }
}

void push_up(int u)
{
    if (tr[u].cnt) tr[u].len = lsh[tr[u].r + 1] - lsh[tr[u].l];
    else if (tr[u].l < tr[u].r)
        tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    else tr[u].len = 0;
}

void modify(int u, int l, int r, int v)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].cnt += v;
        push_up(u);
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u << 1, l, r, v);
    if (mid < r) modify(u << 1 | 1, l, r, v);
    push_up(u);
}

signed main()
{
    int Case = 0;
    while (cin >> n, n)
    {
        Case ++ ;
        lsh.clear();
        for (int i = 1, j = 0; i <= n; i ++ )
        {
            double x1, x2, y1, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            lsh.push_back(y1), lsh.push_back(y2);
            q[ ++ j] = {x1, y1, y2, 1};
            q[ ++ j] = {x2, y1, y2, -1};
        }
        
        sort(q + 1, q + 1 + 2 * n);
        sort(lsh.begin(), lsh.end());
        lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
        build(1, 0, lsh.size() - 2);
        
        double res = 0;
        for (int i = 1; i <= n * 2; i ++ )
        {
            if (i - 1) res += tr[1].len * (q[i].x - q[i - 1].x);
            modify(1, find(q[i].y1), find(q[i].y2) - 1, q[i].w);
        }
        
        printf("Test case #%d\n", Case);
        printf("Total explored area: %.2lf\n\n", res);
    }
    
    return 0;
}

2.可持久化数据结构

1. 可持久化 trie

其实所有可持久化数据结构的思路都差不多应该。

就是将操作路径上所有点都开新点记录下来。

同时用一个数组 rt 记录每个版本的根即可。

P4735 最大异或和(可持久化 Trie)

#include <bits/stdc++.h>

using namespace std;

const int N = 600010, M = N * 25;

int n, m;
int s[N], tr[M][2];
int rt[M], idx;
int mx_id[M];

void insert(int i, int pos, int p, int q)
{
    if (pos < 0)
    {
        mx_id[q] = i;
        return;
    }
    int v = s[i] >> pos & 1;
    if (p) tr[q][v ^ 1] = tr[p][v ^ 1];
    tr[q][v] = ++ idx;
    insert(i, pos - 1, tr[p][v], tr[q][v]);
    mx_id[q] = max(mx_id[tr[q][0]], mx_id[tr[q][1]]);
}

int query(int root, int L, int x)
{
    int p = root;
    for (int i = 23; ~i; i -- )
    {
        int v = x >> i & 1;
        if (mx_id[tr[p][v ^ 1]] >= L) p = tr[p][v ^ 1];
        else p = tr[p][v];
    }
    
    return x ^ s[mx_id[p]];
}

signed main()
{
    rt[0] = ++ idx;
    mx_id[0] = -1;
    insert(0, 23, 0, rt[0]);
    
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        s[i] = s[i - 1] ^ x;
        rt[i] = ++ idx;
        insert(i, 23, rt[i - 1], rt[i]);
    }
    
    for (int i = 1; i <= m; i ++ )
    {
        char ch;
        int l, r, x;
        cin >> ch;
        if (ch == 'A')
        {
            cin >> x;
            n ++ ;
            s[n] = s[n - 1] ^ x;
            rt[n] = ++ idx;
            insert(n, 23, rt[n - 1], rt[n]);
        }
        else
        {
            cin >> l >> r >> x;
            
            cout << query(rt[r - 1], l - 1, x ^ s[n]) << '\n';
        }
    }
    
    return 0;
}

2. 可持久化线段树(主席树)

差不多的思路,把线段树上每个点的记录信息变为左儿

子、右儿子和其他信息即可。

P3919 【模板】可持久化线段树 1(可持久化数组)

#include <bits/stdc++.h>

using namespace std;

const int N = 1000010;

int n, m;
int a[N];
int rt[N], idx;
struct Segment_Tree
{
    int l, r;
    int val;
} tr[N * 4 + N * 21];

int build(int l, int r)
{
    int p = ++ idx;
    if (l == r)
    {
    	tr[p].val = a[l];
    	return p;
    }
    int mid = l + r >> 1;
    tr[p] = {build(l, mid), build(mid + 1, r)};
    
    return p;
}

int modify(int p, int l, int r, int pos, int v)
{
    int q = ++ idx;
    tr[q] = tr[p];
    if (l == r)
    {
    	tr[q].val = v;
    	return q;
    }
    int mid = l + r >> 1;
    if (pos <= mid) tr[q].l = modify(tr[p].l, l, mid, pos, v);
    else tr[q].r = modify(tr[q].r, mid + 1, r, pos, v);
    return q;
}

int query(int p, int l, int r, int pos)
{
    if (l == r) return tr[p].val;
    int mid = l + r >> 1;
    if (pos <= mid) return query(tr[p].l, l, mid, pos);
    else return query(tr[p].r, mid + 1, r, pos);
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
        cin >> a[i];
	rt[0] = build(1, n);
    
    for (int i = 1; i <= m; i ++ )
    {
        int v, pos, c, ch;
        cin >> v >> ch;
        if (ch == 1)
        {
        	cin >> pos >> c;
        	rt[i] = modify(rt[v], 1, n, pos, c);
        }
        else
		{
			cin >> pos;
			
			cout << query(rt[v], 1, n, pos) << '\n';
		} 
    }
    
    return 0;
}

P3834 【模板】可持久化线段树 2(第 k 小数)

感觉板子还是不太熟练。
有一部分线段树二分的思想吧,主要的 trick 是可以用 [1,r] 版本的 cnt 减去 [1,l-1] 版本的。

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;

int n, m;
int a[N];
int rt[N], idx;
vector<int> lsh;
struct Segment_Tree
{
    int l, r;
    int cnt;
} tr[N * 4 + N * 18];

int find(int x)
{
    return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();
}

int build(int l, int r)
{
    int p = ++ idx;
    if (l == r) return p;
    int mid = l + r >> 1;
    tr[p] = {build(l, mid), build(mid + 1, r)};
    
    return p;
}

int insert(int p, int l, int r, int x)
{
    int q = ++ idx;
    tr[q] = tr[p];
    if (l == r)
    {
        tr[q].cnt ++ ;
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
    else tr[q].r = insert(tr[p]. r, mid + 1, r, x);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    
    return q;
}

int query(int p, int q, int l, int r, int x)
{
    if (l == r) return r;
    int v = tr[tr[p].l].cnt - tr[tr[q].l].cnt;
    int mid = l + r >> 1;
    if (x <= v) return query(tr[p].l, tr[q].l, l, mid, x);
    else return query(tr[p].r, tr[q].r, mid + 1, r, x - v);
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        lsh.push_back(a[i]);
    }
    
    sort(lsh.begin(), lsh.end());
    lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
    
    rt[0] = build(0, lsh.size() - 1);
    
    for (int i = 1; i <= n; i ++ )
        rt[i] = insert(rt[i - 1], 0, lsh.size() - 1, find(a[i]));
    while (m -- )
    {
        int l, r, x;
        cin >> l >> r >> x;
        
        cout << lsh[query(rt[r], rt[l - 1], 0, lsh.size() - 1, x)] << '\n';
    }
    
    return 0;
}

3.平衡树 -- treap

BST 和 heap 的结合。

保持二叉搜索树性质的同时,在每个节点记录 val 随

机赋值,然后通过类似堆得维护,使得树的结构稳定在

logn 层,即可做到 O(logn)。

还多了左旋右旋操作。

P3369 【模板】普通平衡树

#include <bits/stdc++.h>

using namespace std;

const int N = 100010, inf = 1e8;

int n;
struct Treap
{
    int l, r;
    int key, val;
    int cnt, size;
} tr[N];
int rt, idx;

int get_node(int key)
{
    int p = ++ idx;
    tr[p] = {0, 0, key, rand(), 1, 1};;
    return p;
}

void pushup(int p)
{
    tr[p].size = tr[p].cnt + tr[tr[p].l].size + tr[tr[p].r].size;
}

void zig(int &p)
{
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    pushup(p), pushup(tr[p].r);
}

void zag(int &p)
{
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    pushup(p), pushup(tr[p].l);
}

void build()
{
    rt = get_node(-inf);
    tr[rt].r = get_node(inf);
    pushup(rt);
    if (tr[tr[rt].l].val < tr[rt].val) zag(rt);
}

void insert(int &p, int x)
{
    if (!p) p = get_node(x);
    else if (tr[p].key == x) tr[p].cnt ++ ;
    else if (tr[p].key < x)
    {
        insert(tr[p].r, x);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    else
    {
        insert(tr[p].l, x);
        if (tr[tr[p].l].val > tr[p].val) zig(p);
    }
    pushup(p);
}

void remove(int &p, int x)
{
    if (!p) return;
    if (tr[p].key == x)
    {
        if (tr[p].cnt > 1) tr[p].cnt -- ;
        else if (tr[p].l || tr[p].r)
        {
            if (!tr[p].l || tr[tr[p].r].val > tr[tr[p].l].val) 
            {
                zag(p);
                remove(tr[p].l, x);
            }
            else
            {
                zig(p);
                remove(tr[p].r, x);
            }
        }
        else p = 0;
    }
    else if (tr[p].key > x) remove(tr[p].l, x);
    else remove(tr[p].r, x);
    pushup(p);
}

int get_rank_by_key(int p, int x)
{
    if (!p) return 1;
    if (tr[p].key == x) return tr[tr[p].l].size + 1;
    else if (tr[p].key > x) return get_rank_by_key(tr[p].l, x);
    else return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, x);
}

int get_key_by_rank(int p, int x)
{
    if (!p) return inf;
    if (tr[tr[p].l].size >= x) return get_key_by_rank(tr[p].l, x);
    else if (tr[tr[p].l].size + tr[p].cnt >= x) return tr[p].key;
    else return get_key_by_rank(tr[p].r, x - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int x)
{
    if (!p) return -inf;
    if (tr[p].key >= x) return get_prev(tr[p].l, x);
    else return max(get_prev(tr[p].r, x), tr[p].key);
}

int get_next(int p, int x)
{
    if (!p) return inf;
    if (tr[p].key <= x) return get_next(tr[p].r, x);
    else return min(get_next(tr[p].l, x), tr[p].key);
}

signed main()
{
    build();
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        int opt, x;
        cin >> opt >> x;
        if (opt == 1) insert(rt, x);
        else if (opt == 2) remove(rt, x);
        else if (opt == 3) printf("%d\n", get_rank_by_key(rt, x) - 1);
        else if (opt == 4) printf("%d\n", get_key_by_rank(rt, x + 1));
        else if (opt == 5) printf("%d\n", get_prev(rt, x));
        else printf("%d\n", get_next(rt, x));
    }
    
    return 0;
}

P6136 【模板】普通平衡树(数据加强版)

#include <bits/stdc++.h>

using namespace std;

const int N = 2000010, inf = INT_MAX;

int n, m;
struct Treap
{
    int l, r;
    int key, val;
    int cnt, size;
} tr[N];
int rt, idx;

int get_node(int key)
{
    int p = ++ idx;
    tr[p] = {0, 0, key, rand(), 1, 1};;
    return p;
}

void pushup(int p)
{
    tr[p].size = tr[p].cnt + tr[tr[p].l].size + tr[tr[p].r].size;
}

void zig(int &p)
{
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    pushup(p), pushup(tr[p].r);
}

void zag(int &p)
{
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    pushup(p), pushup(tr[p].l);
}

void build()
{
    rt = get_node(-inf);
    tr[rt].r = get_node(inf);
    pushup(rt);
    if (tr[tr[rt].l].val < tr[rt].val) zag(rt);
}

void insert(int &p, int x)
{
    if (!p) p = get_node(x);
    else if (tr[p].key == x) tr[p].cnt ++ ;
    else if (tr[p].key < x)
    {
        insert(tr[p].r, x);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    else
    {
        insert(tr[p].l, x);
        if (tr[tr[p].l].val > tr[p].val) zig(p);
    }
    pushup(p);
}

void remove(int &p, int x)
{
    if (!p) return;
    if (tr[p].key == x)
    {
        if (tr[p].cnt > 1) tr[p].cnt -- ;
        else if (tr[p].l || tr[p].r)
        {
            if (!tr[p].l || tr[tr[p].r].val > tr[tr[p].l].val) 
            {
                zag(p);
                remove(tr[p].l, x);
            }
            else
            {
                zig(p);
                remove(tr[p].r, x);
            }
        }
        else p = 0;
    }
    else if (tr[p].key > x) remove(tr[p].l, x);
    else remove(tr[p].r, x);
    pushup(p);
}

int get_rank_by_key(int p, int x)
{
    if (!p) return 1;
    if (tr[p].key == x) return tr[tr[p].l].size + 1;
    else if (tr[p].key > x) return get_rank_by_key(tr[p].l, x);
    else return tr[tr[p].l].size + tr[p].cnt + get_rank_by_key(tr[p].r, x);
}

int get_key_by_rank(int p, int x)
{
    if (!p) return inf;
    if (tr[tr[p].l].size >= x) return get_key_by_rank(tr[p].l, x);
    else if (tr[tr[p].l].size + tr[p].cnt >= x) return tr[p].key;
    else return get_key_by_rank(tr[p].r, x - tr[tr[p].l].size - tr[p].cnt);
}

int get_prev(int p, int x)
{
    if (!p) return -inf;
    if (tr[p].key >= x) return get_prev(tr[p].l, x);
    else return max(get_prev(tr[p].r, x), tr[p].key);
}

int get_next(int p, int x)
{
    if (!p) return inf;
    if (tr[p].key <= x) return get_next(tr[p].r, x);
    else return min(get_next(tr[p].l, x), tr[p].key);
}

signed main()
{
    build();
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
    	int x;
    	cin >> x;
    	insert(rt, x);
    }
    int res = 0, lst = 0;
    for (int i = 1; i <= m; i ++ )
    {
        int opt, x;
        cin >> opt >> x;
        x ^= lst;
        if (opt == 1) insert(rt, x);
        else if (opt == 2) remove(rt, x);
        else if (opt == 3) res ^= (lst = get_rank_by_key(rt, x) - 1);
        else if (opt == 4) res ^= (lst = get_key_by_rank(rt, x + 1));
        else if (opt == 5) res ^= (lst = get_prev(rt, x));
        else res ^= (lst = get_next(rt, x));
    }
    
    cout << res << '\n';
    
    return 0;
}

P2234 [HNOI2002] 营业额统计

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 40010, inf = 1e7;

int n;
struct Treap
{
    int l, r;
    int key, val;
    int cnt, size;
} tr[N];
int rt, idx;

int get_node(int key)
{
    int p = ++ idx;
    tr[p] = {0, 0, key, rand(), 1, 1};;
    return p;
}

void pushup(int p)
{
    tr[p].size = tr[p].cnt + tr[tr[p].l].size + tr[tr[p].r].size;
}

void zig(int &p)
{
    int q = tr[p].l;
    tr[p].l = tr[q].r, tr[q].r = p, p = q;
    pushup(p), pushup(tr[p].r);
}

void zag(int &p)
{
    int q = tr[p].r;
    tr[p].r = tr[q].l, tr[q].l = p, p = q;
    pushup(p), pushup(tr[p].l);
}

void build()
{
    rt = get_node(-inf);
    tr[rt].r = get_node(inf);
    pushup(rt);
    if (tr[tr[rt].l].val < tr[rt].val) zag(rt);
}

void insert(int &p, int x)
{
    if (!p) p = get_node(x);
    else if (tr[p].key == x) tr[p].cnt ++ ;
    else if (tr[p].key < x)
    {
        insert(tr[p].r, x);
        if (tr[tr[p].r].val > tr[p].val) zag(p);
    }
    else
    {
        insert(tr[p].l, x);
        if (tr[tr[p].l].val > tr[p].val) zig(p);
    }
    pushup(p);
}

int get_prev(int p, int x)
{
    if (!p) return -inf;
    if (tr[p].key > x) return get_prev(tr[p].l, x);
    else return max(get_prev(tr[p].r, x), tr[p].key);
}

int get_next(int p, int x)
{
    if (!p) return inf;
    if (tr[p].key < x) return get_next(tr[p].r, x);
    else return min(get_next(tr[p].l, x), tr[p].key);
}

signed main()
{
    build();
    cin >> n;
    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        if (i == 1) res += x;
        else res += min(get_next(rt, x) - x, x - get_prev(rt, x));
        insert(rt, x);
    }
    
    cout << res << '\n';
    
    return 0;
}

4.AC 自动机

这个在第一章复习过了,再复习一遍。

TJOI2013单词(AC自动机)

好,没有什么问题啊。
本题为AC自动机的简单应用。
统计字典树上某个字符串出现次数。
不难发现我们对于一个串,或者说前缀,要统计出有几个以它为后缀的。
考虑倒的来,对于每个后缀,将其计数到前缀上,也就是失配指针指向的,直到跳到根。
统计答案显然。

#include <bits/stdc++.h>

using namespace std;

const int N = 1000010, M = 210;

int n;
int tr[N][30], id[M];
int ne[N], cnt, f[N], q[N];
string str;

void insert(int x)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ )
    {
        int ch = str[i] - 'a';
        if (!tr[p][ch]) tr[p][ch] = ++ cnt;
        p = tr[p][ch];
        f[p] ++ ;
    }
    
    id[x] = p;
}

void build()
{
    int hh = 0, tt = -1;
    
    for (int i = 0; i < 26; i ++ )
        if (tr[0][i]) q[ ++ tt] = tr[0][i];
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        
        for (int i = 0; i < 26; i ++ ) 
        {
            int p = tr[t][i];
            if (!p) tr[t][i] = tr[ne[t]][i];
            else ne[p] = tr[ne[t]][i], q[ ++ tt] = p;
        }
    }
}

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> str;
        insert(i);
    }
    
    build();
    
    for (int i = cnt; i; i -- ) f[ne[q[i]]] += f[q[i]];
    
    for (int i = 1; i <= n; i ++ ) cout << f[id[i]] << '\n';
    
    return 0;
}
posted @ 2025-05-09 20:01  MafuyuQWQ  阅读(10)  评论(0)    收藏  举报