Algorithm improvement-persistent data structure

What data structures can be persisted

Only data structures whose topological order remains unchanged during operation can be persisted (refer to the beginning of the y total teaching video)

Persistible segment tree (chairman tree)

AcWing 255. Kth decimal

A well written blog
. My opinion:

  1. First of all, the data is very large and needs to be discretized. After n numbers are discretized, they are nth (nth refers to the nth largest)
  2. For this question, l and r in each node of the line segment tree maintain lth and rth, which are the lth largest and rth largest numbers in nums after sorting.
  3. After sorting and deduplicating the discretized array nums. Traversing the original array, each insertion of a[i] is a version number. Because the last query is to query the number of kth between [l, r] of the original array. That is, query the two trees of the l-th version and the r-th version. We int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;know how many numbers have been inserted into the left subtree of the line segment tree from version to version. Based on the size relationship between cnt and k, we determine whether to go to the left subtree to continue searching or go to the right subtree l-1. rIf you find the leaf node, you can l == rreturn it directly r或l, because you can find the corresponding value 线段树每个节点中的l和r维护的是lth和rthin lth或rththe nums array that has been sorted and deduplicated directly.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10, M = 1e4 + 10;
int n, m;

struct Node{
    
    
    int l, r, cnt;//l和r维护的是区间的数值,表示的是nums中第l大和第r大之间的一个区间,是一个kth而不是真正的值
}tr[N * 4 + N * 17];

int a[N];
vector<int> nums;
int root[N], idx;

int find(int x)
{
    
    
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();//二分库函数,找到数值为x的数在nums的位置
}

int build(int l, int r)//虽然有很多版本的树,但是他们的骨架是一样的
{
    
                       
    int p = idx ++;
    if (l == r)
    {
    
    
        return p;//idx就是节点编号
    }
    
    int mid = l + r >> 1;
    tr[p].l = build(l, mid); tr[p].r = build(mid + 1, r);

    return p;
}

int insert(int p, int l, int r, int k)
{
    
    
    int q = idx ++;//建立一个新的根节点
    tr[q] = tr[p];//先把旧版本的树的所有信息复制过来
    if (l == r) 
    {
    
    
        tr[q].cnt++;
        return q;
    }
    
    int mid = l + r >> 1;
    if (k <= mid) tr[q].l = insert(tr[p].l, l, mid, k);//递归,说明左节点要更新,不能用之前旧的树的版本信息了
    else tr[q].r = insert(tr[p].r, mid + 1, r, k);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;  // 相当于pushup了
    return q;
}
int query(int q, int p,int l, int r, int k)//bug..不知道为啥调换q和p的顺序不行(传参的时候也是反着的,顺序都对的上)

{
    
    
    if (l == r)
    {
    
    
        return r;//l, r维护的信息就是nums中的kth。
    }
    //cnt求的是在p之后一直到q,有多少个数插入了p的左子树中
    //根据cnt和k的大小关系我们判断接下来是去左子树继续搜索还是去右子树
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    int mid = l + r >> 1;
    if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
    else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);//因为维护的是每个区间有多少个数,所以递归到右边的时候要减去左边计的数
}
int main()
{
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i)
    {
    
    
        cin >> a[i]; nums.push_back(a[i]);
    }
    
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    
    //其实可以不用build(主要是方便理解),在我们insert的过程中就会慢慢build
    root[0] = build(0, nums.size() - 1);//线段树维护kth即可,不用维护真的数值,找到kth再去nums[k]就可以得到值了
    
    for (int i = 1; i <= n; ++ i)
    {
    
    
        root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
    }
    for (int i = 0; i < m; ++ i)
    {
    
    
        int l, r, k;
        cin >> l >> r >> k;
        //查询[l,r]区间中的第k大数,利用前缀和的思想,从[l ~ r]中的数据剔除掉[1 ~ l-1]的数据,
        int x = query(root[r], root[l - 1], 0, nums.size() - 1, k);
        cout << nums[x] << endl;
    }
    return 0;
}

durable trie

AcWing 256. Maximum XOR Sum

  1. I found that basically persistence requires the use of the idea of ​​​​prefix sum. In fact, in a disguised way, it is to make full use of the previous data, find its rules, and find that the idea of ​​​​prefix can be used.
  2. As usual, the root array records the entries of different versions of the tree.
#include <iostream>
using namespace std;
const int M = 3e5 * 2 * 25 + 10, N = 3e5 * 2 + 10;//序列的长度可能不止N,因为还有M次操作,所以*2
int tr[M][2];
int root[N], idx;
int max_id[M];
int s[N];
int n, m;


void insert(int i, int k, int p, int q)
{
    
    
    if (k < 0)
    {
    
    
        max_id[q] = i;
        return ;
    }
    
    int v = s[i] >> k & 1;
    if (p)//可能是p = 0,我们初始化的时候idx = 0没有值
    {
    
    
        tr[q][v ^ 1] = tr[p][v ^ 1];//先把上一个版本的信息记录下来
    }
    
    tr[q][v] = ++ idx;
    
    insert(i, k - 1, tr[p][v], tr[q][v]);//递归的精髓:本轮的p和q就是上一轮的tr[p][v]和tr[q][v]

    max_id[q] = max(max_id[tr[q][1]], max_id[tr[q][0]]);
}

int query(int l, int r, int C)
{
    
    
    int p = root[r];
    for (int i = 23; i >= 0; -- i)
    {
    
    
        int v = C >> i & 1;
        if (max_id[tr[p][v ^ 1]] >= l) p = tr[p][v ^ 1];//可以自己画一棵树,第一个节点的值就是tr[root[r]][v];
        else p = tr[p][v];//迭代往下走
    }
    
    return C ^ s[max_id[p]];
}

int main()
{
    
    
    cin >> n >> m;
    //初始化
    max_id[0] = -1;
   // s[0] = 0; 全局变量默认就是0
    root[0] = ++ idx;//idx = 0当作空
    insert(0, 23, 0, root[0]);
    for (int i = 1; i <= n; ++ i)
    {
    
    
        int x;
        cin >> x;
        s[i] = s[i - 1] ^ x;//发现可持久化数据结构都有前缀和的思想
        root[i] = ++ idx;
        insert(i, 23, root[i - 1], root[i]);
    }
    
    char op[2];
    int l, r, x;
    while (m --)
    {
    
    
        cin >> op;
        if (op[0] == 'A')
        {
    
    
            cin >> x;
            ++ n;
            s[n] = s[n - 1] ^ x;
            root[n] = ++ idx;
            insert(n, 23, root[n - 1], root[n]);
        }
        else 
        {
    
    
            cin >> l >> r >> x;
            cout << query(l - 1, r - 1, s[n] ^ x) << endl;//树的版本号是0~N-1
        }
    }
    return 0;
}

Guess you like

Origin blog.csdn.net/chirou_/article/details/132543414