主席树(历史版本)学习笔记

题目背景

标题即题意

有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集)

题目描述

如题,你需要维护这样的一个长度为 NN 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值

  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

输入输出格式

输入格式:

 

输入的第一行包含两个正整数 N, MN,M, 分别表示数组的长度和操作的个数。

第二行包含NN个整数,依次为初始状态下数组各位的值(依次为 a_iai1 \leq i \leq N1iN)。

接下来MM行每行包含3或4个整数,代表两种操作之一(ii为基于的历史版本号):

  1. 对于操作1,格式为v_i \ 1 \ {loc}_i \ {value}_ivi 1 loci valuei,即为在版本v_ivi的基础上,将 a_{{loc}_i}aloci 修改为 {value}_ivaluei

  2. 对于操作2,格式为v_i \ 2 \ {loc}_ivi 2 loci,即访问版本v_ivi中的 a_{{loc}_i}aloci的值,生成一样版本的对象应为vi

 

输出格式:

 

输出包含若干行,依次为每个操作2的结果。

 

输入输出样例

输入样例#1: 复制
5 10
59 46 14 87 41
0 2 1
0 1 1 14
0 1 1 57
0 1 1 88
4 2 4
0 2 5
0 2 4
4 2 1
2 2 2
1 1 5 91
输出样例#1: 复制
59
87
41
87
88
46


(妥妥的权值线段树)

最终图只是最后一棵线段树的样子,之前的各个线段树依旧在保存着

直接暴力每一个状态,然后记录,超时空!

每一次只修改一个点,不是吗?

于是,我们只要修改这一个点,然后把这条链记录下来,不就可以了?233

这不是熟悉的主席树吗?

是的!

建一棵空树,然后以原数组插入,之后对每一次修改进行一次加链操作(并不是空树)

然后查询就可以了。

直接代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=20000001;
int n,m,cnt;
int a[maxn];
int dis[maxn];//有些不同的就是,我们要记录下来每一次改的值
int rs[maxn];
int ls[maxn];
int rt[maxn];
int build(int l,int r)
{
    int root=++cnt;
    if(l==r)
    {
        dis[root]=a[l];//像极了线段树233
        return root;//还是要记录根的
    }
    int mid=l+r>>1;
    ls[root]=build(l,mid);
    rs[root]=build(mid+1,r);
    return root;
}
int updata(int l,int r,int root,int x,int k)
{
    int newroot=++cnt;
    if(l==r)
    {
        dis[newroot]=x;//如果到叶子了就记录一下新的值
        return newroot;//还是要返回根的
    }
    ls[newroot]=ls[root];
    rs[newroot]=rs[root];
    int mid=l+r>>1;
    if(k<=mid)ls[newroot]=updata(l,mid,ls[newroot],x,k);
    else rs[newroot]=updata(mid+1,r,rs[newroot],x,k);
    return newroot;
}
void query(int l,int r,int root,int x)
{
    if(l==r)
    {
        printf("%d\n",dis[root]);//直接输出,不用差分
        return ;
    }
    int mid=l+r>>1;
    if(x<=mid)query(l,mid,ls[root],x);//直接跑,不用差分了
    else query(mid+1,r,rs[root],x);//直接跑,不用差分了
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);//这次没有这么多神仙操作
    }
    rt[0]=build(1,n);//建树
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        if(y==1)
        {
            int flag;
            scanf("%d",&flag);
            rt[i]=updata(1,n,rt[x],flag,z);//同kth,加链,记录根节点
        }
        if(y==2)
        {
            rt[i]=rt[x];
            query(1,n,rt[x],z);//查询
        }
    }
    return 0;
}
View Code

 

/*
2022.6.22update:
优化了注释
优化了代码
*/
#include
<bits/stdc++.h> using namespace std; const int maxn = 20000001; int n, m, cnt; int a[maxn]; int dis[maxn]; int rs[maxn]; int ls[maxn]; int rt[maxn]; int build(int l, int r) //普普通通的线段树建树(权值线段树) { int p = ++cnt; if (l == r) { dis[p] = a[l]; //与线段树建树相同 return p; } int mid = l + r >> 1; ls[p] = build(l, mid); rs[p] = build(mid + 1, r); return p; } int updata(int l, int r, int root, int x, int k) { int newp = ++cnt; //动态开点 if (l == r) //如果到了叶子节点 { dis[newp] = x; //在树最后新建一个节点 return newp; } else //非叶节点更新左右儿子(更新部分) { ls[newp] = ls[root]; //新节点的左儿子与原相同 rs[newp] = rs[root]; //新节点的右儿子与原相同 } int mid = l + r >> 1; if (k <= mid) ls[newp] = updata(l, mid, ls[newp], x, k); else rs[newp] = updata(mid + 1, r, rs[newp], x, k); return newp; } void query(int l, int r, int root, int x) //查询,从某个根节点出发,查询到的就是那个版本的信息,然后就是普通的线段树单点查询 { if (l == r) { printf("%d\n", dis[root]); return; } int mid = l + r >> 1; if (x <= mid) query(l, mid, ls[root], x); else query(mid + 1, r, rs[root], x); } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } rt[0] = build(1, n); //建树,版本号(根)为0 for (int i = 1; i <= m; i++) //开始在线操作 { int x, y, z; scanf("%d%d%d", &x, &y, &z); if (y == 1) { int flag; scanf("%d", &flag); rt[i] = updata(1, n, rt[x], flag, z); //更新一个点 } if (y == 2) { rt[i] = rt[x]; query(1, n, rt[x], z); //查询一个点 } } system("pause"); return 0; }

 

(完)

posted @ 2019-06-20 00:38  阿基米德的澡盆  阅读(180)  评论(0编辑  收藏  举报