首页 > 编程语言 >算法随笔——主席树(可持久化线段树)

算法随笔——主席树(可持久化线段树)

时间:2024-05-04 16:12:37浏览次数:28  
标签:持久 int 线段 mid 算法 return 随笔 define

介绍

主席树即可持久化线段树,由 hjt 大佬发明。
好像又称函数式线段树。
可以查询序列在任意时间的历史状态。
学习链接 学习链接

主要思路

维护历史状态,若采用直接拷贝整棵树的方式,空间爆炸。
可以发现每次修改只有部分节点发生了改变(其实就是到树根那条链会改变),因此每次只需要记录下来这条链的变化即可。具体来说,相比拷贝整棵树,优化的地方是将新树与旧树没有修改的部分合并,因此只新增加了一条链,空间复杂度 \(O(n \log n)\)。

例题一

P3834 【模板】可持久化线段树 2

思路

可持久化权值线段树,用主席树维护即可。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5+5;

int n,m,a[N],root[N];
struct node
{
    int l,r,sum;
}t[N<<5]; //主席树需要开32倍空间
int tot;

int insert(int k,int l,int r,int x,int v)
{
    int p = ++tot;
    t[p] = t[k]; //复制原树
    if (l == r) {t[p].sum += v;return p;}
    int mid = l + r >> 1;
    if (x <= mid) t[p].l = insert(t[k].l,l,mid,x,v); //更新链
    else t[p].r = insert(t[k].r,mid + 1,r,x ,v);
    t[p].sum = t[t[p].l].sum + t[t[p].r].sum; //pushup
    return p;
}
int query(int p,int q,int l,int r,int x)
{
    if (l == r) return l;
    int mid = l + r >> 1;
    int cnt = t[t[p].l].sum-t[t[q].l].sum ; // 值域 l ~ r 中数的个数 
    if (x <= cnt) return query(t[p].l,t[q].l,l,mid,x);
    else return query(t[p].r,t[q].r,mid + 1,r,x-cnt);
}
int main()
{
    cin >> n >> m;
    vector<int> v;
    for (int i = 1;i <= n;i++) cin >> a[i],v.push_back(a[i]);
    //离散化
    sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
    for (int i= 1; i<= n;i++) a[i] = lower_bound(v.begin(),v.end(),a[i]) - v.begin() + 1;
    const int T = v.size() + 10; //值域范围
    //root[i] 指 第 i 个版本的根
    for (int i = 1;i <= n;i++) root[i] = insert(root[i-1],1,T,a[i],1);
    for (int i = 1;i  <= m;i++) 
    {
        int l,r,k;cin >> l >>r >> k;
        //第 i 个版本保存了 1~i 的数的贡献
        //因为主席树满足可减性
        //因此用第 r 个版本 减去 第 l-1 个版本 可以得到 l~r的贡献
        cout << v[query(root[r],root[l-1],1,T,k)-1] << endl;
    }
    return 0;
}

例题二

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

思路

主席树维护数组的模板,需要支持修改和查询历史版本的数组。

// Problem: P3919 【模板】可持久化线段树 1(可持久化数组)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3919
// Memory Limit: 1024 MB
// Time Limit: 1500 ms
// Author: Eason
// Date:2024-03-26 21:44:45
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define il inline
#define gc getchar
inline int read()
{
	int f=1,k=0;
	char c = getchar();
	while (c <'0' || c > '9')
	{
		if (c=='-') f=-1;
		c=getchar();
	}
	while(c >= '0' && c <= '9')  k = (k << 1)+(k << 3)+(c^48),c=getchar();
	return k*f;
}

const int N = 1e6+5;

int a[N],n,m,root[N];

struct node
{
	int l,r,val;
}t[N << 5];
int tot;
int build(int l,int r) //建树
{
	int p = ++tot;
	if (l == r) {t[p].val = a[l];return p;}
	int mid = l + r >> 1;
	t[p].l = build(l,mid);
	t[p].r = build(mid + 1,r);
	return p; 
}

int insert(int k,int l,int r,int x,int v)
{
	int p = ++tot;
	t[p] = t[k]; //复制原节点
	if (l == r) // 即没有儿子
	{
		t[p].val = v;//直接赋值
		return p;
	}
	int mid =  l + r >> 1;
	if (x <= mid) t[p].l = insert(t[k].l,l,mid,x,v);
	else t[p].r = insert(t[k].r,mid + 1,r,x,v);
	return p;
}

int query(int k,int l,int r,int x) //查询
{
	if (x < l || x > r) return 0;
	if (l == r) return t[k].val;
	int mid = l + r >> 1;
	return query(t[k].l,l,mid,x) + query (t[k].r,mid + 1,r,x);
}

int main()
{
	cin >> n >> m;
	for (int i = 1;i <= n;i++) a[i] = read();
	root[0] = build(1,n);
	for (int i = 1;i <= m;i++)
	{
		int x = read(),op = read(),y = read();
		if (op == 1)
		{
			int z = read();
			root[i] = insert(root[x],1,n,y,z);
		}
		else
		{
			printf("%d\n",query(root[x],1,n,y));
			root[i] = root[x];
		}
	}
	return 0;
}

标签:持久,int,线段,mid,算法,return,随笔,define
From: https://www.cnblogs.com/codwarm/p/18097819

相关文章

  • leetcod算法热题--移动零
    题目给定一个数组nums,编写一个函数将所有0移动到数组的末尾,同时保持非零元素的相对顺序。请注意,必须在不复制数组的情况下原地对数组进行操作。示例1:输入:nums=[0,1,0,3,12]输出:[1,3,12,0,0]示例2:输入:nums=[0]输出:[0]提示:1<=nums.length<=......
  • leetcode算法热题--最长连续序列
    题目给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。示例1:输入:nums=[100,4,200,1,3,2]输出:4解释:最长数字连续序列是[1,2,3,4]。它的长度为4。示例2:输入:nums=......
  • 个人算法竞赛模板(2024.5.4)
    精简版:#include<algorithm>#include<cmath>#include<cstring>#include<iostream>#include<map>#include<queue>#include<set>#include<stack>#include<string>#include<vector>#include<......
  • 网友开放的开源项目:网页版的A*算法可视化演示程序
    相关项目:https://xueqiaoxu.me/#projects项目介绍:AJavaScriptpath-findinglibraryforgridbasedgames.Italsocomesalongwithaninteractivevisualizationofnumerousstrategies.项目地址:https://github.com/qiao/PathFinding.js演示地址:https://qia......
  • 算法随笔——manacher
    非常好学习资料manacher求最长回文子串暴力枚举回文中心\([1,n]\),暴力向两边拓展,然后\(checkmax\)。时间复杂度\(O(n^2)\)可以用二分哈希优化至\(O(n\logn)\)算法思路当求解第\(i\)个字符为回文中心的时候,已经知道了\([1,i-1]\)之间的信息。于是引入\(p[i]\):......
  • A* 算法、PathFinding问题中的 allow diagonal 和 don't cross corners,以及 .map文件
    地址:https://webdocs.cs.ualberta.ca/~nathanst/papers/benchmarks.pdf关于地图文件:.map文件的格式参考:https://movingai.com/benchmarks/formats.html......
  • [随笔] 快速幂
    没办法老师让预习,我就只能把我快一年前的快速幂翻出来不放洛谷了,老师容易窥探>﹏< 老师の问题1.快速幂的原理是什么每一步都把指数分成两半,而相应的底数做平方运算2.如果求2的23次方,快速幂的具体过程是什么∵23=2^0+2^1+2^2+2^4∴2^23=2^2^0*2^2^1*2^2^2*2^2^4 位......
  • Kosaraju 算法
    引入Kosaraju算法用于求解强连通分量,在稠密图下复杂度会比tarjan算法要优秀。(?过程对整个图进行搜索,并且将没一个顶点按照DFS序压入栈中。建一个反图。对于栈中的每一个点再反图上跑一遍DFS,现在跑出来的子图即为一个强连通分量。标记这几个点。重复执行操作......
  • m基于LDPC编译码的matlab误码率仿真,对比SP,MS,NMS以及OMS四种译码算法
    1.算法仿真效果matlab2022a仿真结果如下:    2.算法涉及理论知识概要       低密度奇偶校验码(LDPC)译码是现代通信系统中一种高效的错误校正技术,广泛应用于无线通信、卫星通信和数据存储等领域。LDPC码因其良好的纠错性能和接近香农极限的潜力而受到重视。本文......
  • 算法基础课笔记
    二分整数二分有单调性一定可以二分,二分不一定有单调性数的范围intmain(){scanf("%d%d",&n,&m);for(inti=0;i<n;i++)scanf("%d",&q[i]);while(m--){intx;scanf("%d",&x);intl......