主席树学习

很好的博客:https://blog.csdn.net/qq_39809664/article/details/79934516

可持久化数组

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define ll long long
using namespace std;
const int maxn=1000000+10101;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch==‘-‘)f=-1;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-‘0‘;
    return x*f;
}
int n,m,a[maxn],tree[maxn*20],rt[maxn*20],cnt,lc[maxn*20],rc[maxn*20];//rt[i]为插入i个点后的树的根节点编号
void build(int &k,int l,int r){
    k=++cnt;
    if(l==r){tree[k]=a[l];return ;}
    int mid=l+r>>1;
    build(lc[k],l,mid);build(rc[k],mid+1,r);
    return ;
}
void change(int &k,int pre,int l,int r,int q,int v){
    k=++cnt;lc[k]=lc[pre];rc[k]=rc[pre];tree[k]=tree[pre];
    if(l==r){tree[k]=v;return ;}
    int mid=l+r>>1;
    if(mid<q)change(rc[k],rc[pre],mid+1,r,q,v);
    else change(lc[k],lc[pre],l,mid,q,v);
}
int query(int k,int l,int r,int pos){
    if(l==r)return tree[k];
    int mid=l+r>>1;
    if(pos<=mid)return query(lc[k],l,mid,pos);
    else return query(rc[k],mid+1,r,pos);
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)a[i]=read();
    build(rt[0],1,n);
    for(int i=1;i<=m;i++){
        int vi=read(),opt=read(),x=read();
        if(opt==1)change(rt[i],rt[vi],1,n,x,read());
        else printf("%d\n",query(rt[vi],1,n,x)),rt[i]=rt[vi];
    }
    return 0;
}

可持久化线段树 1(主席树)

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define ll long long
using namespace std;
const int maxn=1000000+10101;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch==‘-‘)f=-1;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-‘0‘;
    return x*f;
}
int len,n,m,a[maxn],b[maxn],cnt,rt[maxn*20];
struct wzq{
    int sum,lr,rc;
}tre[maxn*20];
void build(int &k,int l,int r){
    k=++cnt;
    if(l==r)return ;
    int mid=l+r>>1;
    build(tre[k].lr,l,mid);build(tre[k].rc,mid+1,r);
    return ;
}
void update(int l,int r,int &now,int pre,int pos){
    tre[++cnt]=tre[pre];
    now=cnt;
    tre[cnt].sum++;
    if(l==r)return ;
    int mid=l+r>>1;
    if(pos<=mid)update(l,mid,tre[now].lr,tre[pre].lr,pos);
    else update(mid+1,r,tre[now].rc,tre[pre].rc,pos);
    return ;
}
int query(int l,int r,int x,int y,int pos){
    if(l==r)return l;
    int xx=tre[tre[y].lr].sum-tre[tre[x].lr].sum;
    int mid=l+r>>1;
    if(xx>=pos)return query(l,mid,tre[x].lr,tre[y].lr,pos);
    return query(mid+1,r,tre[x].rc,tre[y].rc,pos-xx);
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)a[i]=read(),b[i]=a[i];
    sort(a+1,a+n+1);
    len=unique(a+1,a+1+n)-a-1;
    build(rt[0],1,m);
    for(int i=1;i<=n;i++){
        int t=lower_bound(a+1,a+1+m,b[i])-a;
        update(1,m,rt[i],rt[i-1],t);
    }
    for(int i=1;i<=m;i++){
        int l=read(),r=read(),k=read();
        printf("%d\n",a[query(1,m,rt[l-1],rt[r],k)]);
    }
    return 0;
}

[CQOI2015]任务查询系统

这道题可以对每秒建棵权值线段树,并以1~lim(优先级的最大值)为区间大小记录个数,这样就可以处理在x秒的第k小前缀和了,

但是lim因为很大,可能会导致MLE,所以要离散化一下

// luogu-judger-enable-o2
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define ll long long
using namespace std;
const int maxn=201010;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch==‘-‘)f=-1;
    for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-‘0‘;
    return x*f;
}
int n,m,cnt,tot,rt[maxn],to[maxn],h[maxn];
ll ans=1;
struct wzq{ll sum;int sz,lr,rc;}tre[maxn*128];
struct hhh{int pos,v;}b[maxn];
bool cmp(hhh i,hhh j){return i.pos<j.pos;}
void build(int &k,int l,int r){
    k=++cnt;
    if(l==r)return ;
    int mid=l+r>>1;
    build(tre[k].lr,l,mid);build(tre[k].rc,mid+1,r);
    return ;
}
void change(int &k,int pre,int l,int r,int tag){
    k=++cnt;
    tre[k]=tre[pre];tre[k].sum+=tag;
    if(tag<0)tre[k].sz--;
    else tre[k].sz++;
    if(l==r)return ;
    int mid=l+r>>1;
    if(abs(tag)<=h[mid])change(tre[k].lr,tre[pre].lr,l,mid,tag);
    else change(tre[k].rc,tre[pre].rc,mid+1,r,tag);
    return ;
}
ll query(int k,int l,int r,int pos){
    if(l==r)return 1ll*tre[k].sum/tre[k].sz*min(pos,tre[k].sz);
    int mid=l+r>>1;
    if(tre[tre[k].lr].sz>=pos)return 1ll*query(tre[k].lr,l,mid,pos);
    return 1ll*tre[tre[k].lr].sum+query(tre[k].rc,mid+1,r,pos-tre[tre[k].lr].sz);
}
int main(){
    m=read();n=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read(),z=read();
        b[++tot].pos=x;b[tot].v=z;
        b[++tot].pos=y+1;b[tot].v=-z;
        h[i]=z;
    }
    sort(h+1,h+m+1);
    int tot1=unique(h+1,h+m+1)-h-1;
    sort(b+1,b+tot+1,cmp);build(rt[0],1,tot1);
    for(int i=1;i<=tot;i++)change(rt[i],rt[i-1],1,tot1,b[i].v);
    for(int i=tot;i>=1;i--)if(b[i].pos!=b[i+1].pos)to[b[i].pos]=i;
    for(int i=1;i<=m;i++)if(!to[i])to[i]=to[i-1];
    for(int i=1;i<=n;i++){
        ll x=read(),a=read(),b=read(),c=read();
        ll k=(1ll*a*ans+b)%c+1;
        x=rt[to[x]];
        if(tre[x].sz<=k)ans=1ll*tre[x].sum;
        else ans=query(x,1,tot1,k);
        printf("%lld\n",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/wzq--boke/p/10079992.html

时间: 2024-10-14 16:22:14

主席树学习的相关文章

[知识学习] 主席树

这两天学习了主席树,基本上搞懂了主席树是怎么操作的 主席树,是一种可持久化线段树.最简单的操作就是维护静态区间第 \(k\) 小 主席树通过维护历史版本,实现查询区间的有关操作 主席树的原理 假设现在有这么一个序列:\(4,1,3,5,2\) 问如何求出区间[1,3]内大小为第二的数? 利用大眼观察法,很显然是3 那么让计算机去怎么实现呢?它又没有眼睛 对于这个序列,我们可以先建一颗空的权值线段树,命名为"树0"(方便后面的使用),如图: 别告诉我你不知道什么是权值线段树,自己去百度:

[学习笔记]主席树

权值线段树 线段树上每个区间记录的是区间内所有数出现次数的总和. 然后就可以求出整棵线段树的第k大的数了(类似于二叉查找树?) 主席树 建立$n$棵上述的权值线段树,第$i$棵表示$a_1-a_i$的所有数组成的权值线段树. 用可持久化线段树的思想会发现,第$i$棵线段树与第$(i-1)$棵线段树之间只有$logn$个区间值是不同的,所以每次只要新建$logn$个区间,总复杂度是$O(nlogn)$. 区间查询类似于前缀和. 例题 bzoj4408

主席树(函数式线段树)学习小结(附手绘讲解图片)

主席树是一种离线数据结构,是由很多棵线段树组成的. 第i棵线段树存的是前i个数的信息: 每一个线段存数字的出现次数(因此建树之前要离散化). 那么n棵线段树不会MLE吗? 当然会了! 但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 具体建树方法建下图: 序列为 1 3 4 2 那么如果要询问i-j之间数字出现的次数怎么办呢? 因为每一棵线段树的区间都是相同的,所以要求l-r之间的数字的出现次数只要用前r位出现的次数减去前l-1

主席树的学习

前言 主席树可真是个好东西 之前一直都觉得挺难的 今天一看 woc这么简单! 怎么可能,我还是太蒟蒻了 感谢akakw1大佬的指导! 正文: 一.前置知识及算法思路 1.可持久化 因为主席树是可持久化线段树,所以还是有必要了解一下可持久化 可持久化的数据结构是可以支持访问任一历史版本的(也就是每一次修改操作之前的情况) 2.如何实现 以可持久化线段树为例: 很自然的,我们可以想到对于每一个版本开一个线段树 但考虑到这样做的空间复杂度是\(O(k*n*4)\) (k为修改次数) 果断放弃 3.优化

BZOJ_3207_花神的嘲讽计划1_(Hash+主席树)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3207 给出一个长度为\(n\)的串,以及\(m\)个长度为\(k\)的串,求每个长度为\(k\)的串在原串\([x,y]\)区间是否出现过. 分析 这道题要求对比长度为\(k\)的串,于是我们把这些串的Hash值都算出来,问题就转化成了求\([x,y]\)的区间中是否出现过某Hash值. 求区间中某一个值出现了多少次,可以用主席树. p.s. 1.学习了主席树指针的写法,比数组慢好多啊...

poj_2104: K-th Number 【主席树】

题目链接 学习了一下主席树,感觉具体算法思路不大好讲.. 大概是先建个空线段树,然后类似于递推,每一个都在前一个"历史版本"的基础上建立一个新的"历史版本",每个历史版本只需占用树高个空间(好神奇!) 查询时这道题是通过"历史版本"间作差解决 *另外提一下,在建立"历史版本"的过程中,是"新建",而不是"更新",是先copy过来原有的,再按个人需求改成自己的,所产生的一个新的"

主席树初学 SPOJ3267

别的没管,直接上的kuangbin代码,懂是基本懂了,然而主席树博大精深们还要多多学习. #include <stdio.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <stri

【BZOJ 1901】【Zju 2112】 Dynamic Rankings 动态K值 树状数组套主席树模板题

达神题解传送门:http://blog.csdn.net/dad3zz/article/details/50638360 说一下我对这个模板的理解: 看到这个方法很容易不知所措,因为动态K值需要套树状数组,而我一开始根本不知道该怎么套,, 学习吧,,, 然后我自己脑补如果不套会如何?后来想到是查询O(logn),修改是O(nlogn),很明显修改的复杂度太大了,为了降低修改的复杂度,我们只得套上树状数组来维护前缀和使它的n的复杂度降低为logn,从而修改的复杂度变为O(log2n).但因为我们套

zoj2112 主席树动态第k大 (主席树&amp;&amp;树状数组)

Dynamic Rankings Time Limit: 10 Seconds      Memory Limit: 32768 KB The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They