Mejora del algoritmo: estructura de datos persistente.

¿Qué estructuras de datos se pueden conservar?

Solo se pueden conservar las estructuras de datos cuyo orden topológico permanece sin cambios durante la operación (consulte el comienzo del video de enseñanza total)

Árbol de segmento persistente (árbol presidente)

AcWing 255. Késimo decimal

Un blog
bien escrito . Mi opinión:

  1. En primer lugar, los datos son muy grandes y deben discretizarse. Después de discretizar n números, son enésimos (nésimo se refiere al enésimo más grande)
  2. Para esta pregunta, l y r en cada nodo del árbol de segmentos de línea mantienen lth y rth, que son los números l-ésimo más grande y r-ésimo más grande en números después de la clasificación.
  3. Después de ordenar y deduplicar los números de matriz discretizados. Recorra la matriz original y cada vez que se inserte un [i], será un número de versión. Porque la consulta final es consultar el k-ésimo número entre [l, r] de la matriz original. Es decir, consultar los dos árboles de la versión l-ésima y la versión r-ésima. Sabemos cuántos números se han insertado en el subárbol izquierdo del árbol int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;de segmentos de línea de versión en versión. Según la relación de tamaño entre cnt y k, determinamos si continuar buscando en el subárbol izquierdo o en el subárbol derecho l-1. rSi se busca el nodo hoja, se puede l == rdevolver directamente r或l, porque el valor correspondiente se puede encontrar 线段树每个节点中的l和r维护的是lth和rthen lth或rthla matriz nums que se ha ordenado y deduplicado directamente.
#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;
}

prueba duradera

AcWing 256. Suma XOR máxima

  1. Descubrí que básicamente la persistencia requiere el uso de la idea de suma de prefijo, de hecho, de manera disfrazada, es hacer un uso completo de los datos anteriores, encontrar sus reglas y descubrir que la idea de Se puede utilizar el prefijo.
  2. Como es habitual, la matriz raíz registra las entradas de diferentes versiones del árbol.
#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;
}

Supongo que te gusta

Origin blog.csdn.net/chirou_/article/details/132543414
Recomendado
Clasificación