可持久化线段树&主席树

可持久化线段树

注意空间开大10~40倍

模板

修改查询历史版本

其实操作很简单,就root开个数组,然后每次修改旧版本时,边记录旧的,边开一个新的,新的先粘旧的,再修改

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=4e7+10;//开大40倍 
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,a[N];
int ls[N],rs[N],val[N],rt[N],cnt;

#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
	p=++cnt;
	if(l==r) { val[p]=a[l];return; }
	build(l,mid,ls[p]);
	build(mid+1,r,rs[p]);
}

void modify(int l,int r,int pos,int v,int pre,int &p) {
	p=++cnt;
	ls[p]=ls[pre],rs[p]=rs[pre],val[p]=val[pre];
	if(l==r) { val[p]=v;return;	}
	if(pos<=mid) modify(l,mid,pos,v,ls[pre],ls[p]);
	else modify(mid+1,r,pos,v,rs[pre],rs[p]);
}

int query(int l,int r,int pos,int p) {
	if(l==r) return val[p];
	if(pos<=mid) return query(l,mid,pos,ls[p]);
	else return query(mid+1,r,pos,rs[p]);
}

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

主席树

名字来历好像是发明这玩意的人名字缩写和主席一样....

模板

求静态区间第 k 小

首先我们要将所有数字离散化。主席树相当于是在每个位置(每一个前缀)维护了一个线段树,线段树的节点是一个区间\([x, y]\),这里的\(x\)\(y\)都是离散后 数的编号。

主席树节点中维护的值,是 \(1-i\) 之间这个区间内出现了的数的次数。然后当我们查询的时候,就是利用到了前缀和的思想。

详细讲解

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=4e7+10;//开大40倍 
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
inline void Max(int &x,int y){if(x<y)x=y;}
inline void Min(int &x,int y){if(x>y)x=y;}
int n,m,a[N],b[N],pos;
int ls[N],rs[N],val[N],rt[N],cnt;

#define mid ((l+r)>>1)
void build(int l,int r,int &p) {
	p=++cnt;
	if(l==r) return; 
	build(l,mid,ls[p]);
	build(mid+1,r,rs[p]);
}

void modify(int l,int r,int pos,int &p) {
	int x=++cnt; 
	ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
	if(l==r) return;
	if(pos<=mid) modify(l,mid,pos,ls[p]);
	else modify(mid+1,r,pos,rs[p]);
}

int query(int l,int r,int x,int y,int rk) {
	if(l==r) return l;
	int num=val[ls[y]]-val[ls[x]];
	if(num>=rk) return query(l,mid,ls[x],ls[y],rk);
	else return query(mid+1,r,rs[x],rs[y],rk-num);
}

int main() {
	n=read();m=read();
	for(int i=1;i<=n;i++) b[i]=a[i]=read();
	sort(b+1,b+1+n);
	int len=unique(b+1,b+1+n)-b-1;
	build(1,len,rt[0]);
	
	for(int i=1;i<=n;i++) {
		pos=lower_bound(b+1,b+1+len,a[i])-b;
		modify(1,len,pos,rt[i]=rt[i-1]);
	}
	for(int i=1,k,x,y;i<=m;i++) {
		x=read();y=read();k=read();
		int ans=query(1,len,rt[x-1],rt[y],k);
		printf("%d\n",b[ans]);
	}
	return 0;
}

KUR-Couriers

就是个板子题。。。

直接把数组插入主席树,询问时候分三步

\(if(2*(val[ls[y]]-val[ls[x]])>v) ~return ~query(l,mid,ls[x],ls[y],v);\)

\([l,mid]\)里能不能满足

\(if(2*(val[rs[y]]-val[rs[x]])>v) ~return ~query(mid+1,r,rs[x],rs[y],v);\)

\([mid+1,r]\)里能不能满足

都不能就 \(return~ 0;\)

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=4e7+10;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
#define mid ((l+r)>>1)

int n,m,x;
int cnt,ls[N],rs[N],val[N],root[N];

void modify(int l,int r,int pos,int &p){
    int x=++cnt;
    ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
    if(l==r) return;
    if(pos<=mid) modify(l,mid,pos,ls[p]);
    else modify(mid+1,r,pos,rs[p]);
}

int query(int l,int r,int x,int y,int v){
    if(l==r) return l;
    if(2*(val[ls[y]]-val[ls[x]])>v) return query(l,mid,ls[x],ls[y],v);
    if(2*(val[rs[y]]-val[rs[x]])>v) return query(mid+1,r,rs[x],rs[y],v);
    return 0;
}

int main(){
    n=read();m=read();
    root[0]=0;
    for(int i=1;i<=n;i++){
        x=read();
        root[i]=root[i-1],modify(1,n,x,root[i]);
    }
    for(int i=1,x,y;i<=m;i++){
        x=read();y=read();
        printf("%d\n",query(1,n,root[x-1],root[y],y-x+1));
    }
    return 0;
}

CF840D Destiny

和上题一毛一样吧

就除以2变成除以k

注意不能和上面那个一样给分子乘k——反正会出锅,题目要求严格大于,所以就直接除吧,自带下取整

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
//typedef long long ll;
#define int long long
const int N=10000005;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,m,rt[N];
int a[N];
int tree_cnt,ls[N],rs[N],val[N];
#define mid ((l+r)>>1) 

void build(int l,int r,int &p) {
	if(!p) p=++tree_cnt;
	if(l==r) return;
	build(l,mid,ls[p]);
	build(mid+1,r,rs[p]);
}

void modify(int l,int r,int pos,int &p) {
	int x=++tree_cnt;
	ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1,p=x;
	if(l==r) return;
	if(pos<=mid) modify(l,mid,pos,ls[p]);
	else modify(mid+1,r,pos,rs[p]);
}

int k;
int query(int l,int r,int x,int y,int v) {
	if(l==r) return l;
	int ans=0;
	if(val[ls[y]]-val[ls[x]]>v) {
		ans=query(l,mid,ls[x],ls[y],v);
		if(ans!=-1) return ans;
	}
	if(val[rs[y]]-val[rs[x]]>v) {
		ans=query(mid+1,r,rs[x],rs[y],v);
		if(ans!=-1) return ans;
	}
	return -1;
}

signed main() {
	n=read();m=read();
	for(int i=1;i<=n;i++) 
		rt[i]=rt[i-1],modify(1,n,read(),rt[i]);
	for(int i=1;i<=m;i++) {
		int l=read(),r=read();k=read();
		printf("%lld\n",query(1,n,rt[l-1],rt[r],(r-l+1)/k));
	}
	return 0;
}

Count on a tree

很明显主席树有个差分的思想,刚才序列上的问题,变成了树上,其实很简单,树上差分,就\(u,v++, lca,fa[lca]--\) ,然后树剖求个\(lca\)就好,注意这里的\(modify\)和上面的都不同,因为是树上问题,所以不能简单继承前一个,而是要继承\(fa\)

\(dfn\)序值域,求权值第\(k\)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=101000;
const int M=4001000;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int hd[N],to[N<<1],nxt[N<<1],tot;
inline void add(int x,int y) {
	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}
int n,m;
int son[N],dep[N],fa[N],siz[N];
void dfs_son(int x,int f) {
	fa[x]=f;siz[x]=1;dep[x]=dep[f]+1;
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==f) continue;
		dfs_son(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y;
	}
}

int top[N];
void dfs_chain(int x,int tp) {
	top[x]=tp;
	if(son[x]) dfs_chain(son[x],tp);
	for(int i=hd[x];i;i=nxt[i]) {
		int y=to[i];
		if(y==fa[x]||y==son[x]) continue;
		dfs_chain(y,y);
	}
}

int LCA(int x,int y) {
	while(top[x]!=top[y]) {
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	return dep[x]<dep[y]?x:y;
}

#define mid ((l+r)>>1)
int ls[M],rs[M],rt[N],val[M],cnt;
void modify(int l,int r,int pos,int p,int &x) {
	x=++cnt;
	ls[x]=ls[p],rs[x]=rs[p],val[x]=val[p]+1;
    if(l==r) return;
	if(pos<=mid) modify(l,mid,pos,ls[p],ls[x]);
	else modify(mid+1,r,pos,rs[p],rs[x]);
}

int a[N],b[N],len;
void build(int x) {
	modify(1,len,a[x],rt[fa[x]],rt[x]);
	for(int i=hd[x];i;i=nxt[i]) 
		if(to[i]!=fa[x]) 
			build(to[i]);
}

int query(int l,int r,int k,int u,int v,int x,int y) {
	if(l==r) return l;
	int tmp=val[ls[u]]+val[ls[v]]-val[ls[x]]-val[ls[y]];
	if(k<=tmp) return query(l,mid,k,ls[u],ls[v],ls[x],ls[y]);
	else return query(mid+1,r,k-tmp,rs[u],rs[v],rs[x],rs[y]);
}

int main() {
	n=read();m=read();
	for(int i=1;i<=n;i++) a[i]=b[i]=read();
	sort(b+1,b+1+n);
	len=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+1+len,a[i])-b;
	for(int i=1;i<n;i++) {
		int u=read(),v=read();
		add(u,v);add(v,u);
	}
	dfs_son(1,0);
	dfs_chain(1,1);
	rt[0]=++cnt;
	build(1);
	int ans=0;
	for(int i=1;i<=m;i++) {
		int u=read(),v=read(),k=read();
		u^=ans;
		int lca=LCA(u,v);
		ans=b[query(1,len,k,rt[u],rt[v],rt[lca],rt[fa[lca]])];
		printf("%d\n",ans);
	}
	return 0;
}




P3939 数颜色

假主席树

CF893F Subtree Minimum Query

主席树二维数点

第一维时间戳按深度一层一层往下,在上面建第二维权值,由前缀和思想,对于 每个询问就找dep[x]+k的深度上访问最小值就好

对dep值域求val最小

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=100500;
const int inf=1e9+10;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,m,val[N];
int ls[N<<5],rs[N<<5],mn[N<<5];
int rt[N],L[N],R[N],dfn_cnt,dep[N];
int hd[N],to[N*10],nxt[N*10],tot;
inline void add(int x,int y) {
	to[++tot]=y;nxt[tot]=hd[x];hd[x]=tot;
}

vector<int> v[N];
void dfs(int x,int fa) {
	L[x]=++dfn_cnt;
	dep[x]=dep[fa]+1;
	v[dep[x]].push_back(x);
	for(int i=hd[x];i;i=nxt[i]) 
		if(to[i]!=fa)
			dfs(to[i],x);
	R[x]=dfn_cnt;
}

#define mid ((l+r)>>1)
int cnt;
void modify(int l,int r,int pos,int v,int &p) {
	int x=++cnt;
	ls[x]=ls[p];rs[x]=rs[p],mn[x]=min(mn[p],v),p=x;
	if(l==r) return;
	if(pos<=mid) modify(l,mid,pos,v,ls[p]);
	else modify(mid+1,r,pos,v,rs[p]);
}

int query(int l,int r,int L,int R,int p) {
	if(L<=l&&r<=R) return mn[p];
	int res=inf;
	if(L<=mid) res=min(res,query(l,mid,L,R,ls[p]));
	if(R>mid) res=min(res,query(mid+1,r,L,R,rs[p]));
	return res;
}

int main() {
	n=read();int root=read();
	for(int i=1;i<=n;i++) val[i]=read();
	int x,y;
	for(int i=1;i<n;i++) {
		x=read();y=read();
		add(x,y);add(y,x);
	}
	mn[0]=inf;
	dfs(root,0);
	for(int i=1;i<=n;i++,rt[i]=rt[i-1]) 
		for(auto x:v[i])
			modify(1,n,L[x],val[x],rt[i]);
	
	m=read();
	int ans=0;
	while(m--) {
		int x=(read()+ans)%n+1,k=(read()+ans)%n;
		printf("%d\n",ans=query(1,n,L[x],R[x],rt[min(dep[x]+k,n)]));
	}
	return 0;
}

FJOI2016神秘数

首先对于一个集合,假设它能表示的数为\([0,sum]\)

在一个区间上,可以把区间里的数拿出来,从小到大排序为\(a_1,a_2\cdots a_n\),在答案不为\(1\)的情况下明显有\(a_1=1\).

然后从小到大把数加进集合,假设当前集合内数的上界是\(Max\),那么要加入的数一定在\([Max+1,sum+1]\)内才可以。

把这个值域内的所有数全部加入,设这个值域内的数的和为\(tmp\),此时\(Max\)要变成\(sum+1\)(因为之前的数都加过了),而\(sum\)要加上\(tmp\).

如果某个时刻\(tmp=0\),那么答案就是\(sum+1\).

因为这个涉及到区间内求某段权值区间内所有元素的和,所以要用到主席树。

因为是迭代更新\(Max\)\(sum\),再加上主席树的复杂度,总的复杂度是\(\Theta(mlog_2^2 \sum a_i)\).

注意这里\(query\)函数的写法,必须严格这样写

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;
typedef long long ll;
const int N=100005;
const int inf=1e9;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f*x;
}
int n,rt[N];
int ls[N<<5],rs[N<<5];
ll val[N<<5];
int cnt;
#define mid ((l+r)>>1)
void modify(int l,int r,int v,int &p) {
	int x=++cnt;
	ls[x]=ls[p];rs[x]=rs[p];
	val[x]=val[p]+v; p=x;
	if(l==r) return;
	if(v<=mid) modify(l,mid,v,ls[p]);
	else modify(mid+1,r,v,rs[p]);
}

ll query(int l,int r,int L,int R,int x,int y) {
	if(!(val[y]-val[x])) return 0;
	if(l==L&&r==R) return val[y]-val[x];
	if(R<=mid) return query(l,mid,L,R,ls[x],ls[y]);
	else if(L>mid) return query(mid+1,r,L,R,rs[x],rs[y]);
	else return query(l,mid,L,mid,ls[x],ls[y])+query(mid+1,r,mid+1,R,rs[x],rs[y]);
}

int main() {
	n=read();
	for(int i=1;i<=n;i++,rt[i]=rt[i-1]) 
		modify(1,inf,read(),rt[i]);
	int m=read();
	while(m--) {
		int l=read(),r=read();
		ll mx=0,sum=0;
		while(1) {
			ll tmp=query(1,inf,mx+1,sum+1,rt[l-1],rt[r]);
			if(!tmp) break;
			mx=sum+1;sum+=tmp;
		}
		printf("%lld\n",sum+1);
	}
	return 0;
}

MIDDLE

人生第二道黑题

首先是求中位数,这里有一种思想转换

­二分答案\(ans\),把序列中小于\(ans\)的设为\(-1\),大于等于\(ans\)的设为\(1\)(复杂度O(n)),题目变成了求指定区间内的最大子段和(判断>=0即合理),线段树可以解决

­但是不能每次二分一个\(ans\)(何况还有\(q\)个询问)都建一遍树,因此可以考虑对于不同的\(ans\)的值建一颗主席树

­先把\(ans=1\)(离散化之后,额我没离散化)的线段树建出来,然后ans增加的时候树上只需要单点修改(套主席树的板子)

\(check\)详解:

\([a,b]\)求一个最大后缀子段和, \([c, d]\)求一个最大前缀子段和, \([b+1, c-1]\)求一个和(若\(c\)\(b\)左边不求)

image-20200722104346567

注意!!!$q[i]=(q[i]+ans)%n+1; $一定要加\(1\) ,不然会挂

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=1e6+10;
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct node{
    int v,pos;
    node(){}
    node(int a,int b):v(a),pos(b){}
}a[N];
struct tree{
    int sum,lmx,rmx;
    tree(){}
    tree(int a,int b,int c):sum(a),lmx(b),rmx(c){}
    tree operator + (const tree &x)const {
        return tree(sum+x.sum,max(lmx,sum+x.lmx),max(x.rmx,rmx+x.sum));
    }
}t[N];

int n,m,ans;
int q[10];
int cnt,ls[N],rs[N],val[N],root[N];
#define mid ((l+r)>>1)
void build(int l,int r,int &p){//建空树
    p=++cnt;
    if(l==r){ t[p]=tree(1,1,1); return; }
    build(l,mid,ls[p]);
    build(mid+1,r,rs[p]);
    t[p]=t[ls[p]]+t[rs[p]];
}

void modify(int l,int r,int pos,int &p){
    int x=++cnt;
    ls[x]=ls[p],rs[x]=rs[p],t[x]=t[p],p=x;
    if(l==r) { t[p]=tree(-1,-1,-1); return; }
    if(pos<=mid) modify(l,mid,pos,ls[p]);
    else modify(mid+1,r,pos,rs[p]);
    t[p]=t[ls[p]]+t[rs[p]];
}

tree query(int l,int r,int L,int R,int p){
    if(L<=l&&r<=R) return t[p];
    if(R<=mid) return query(l,mid,L,R,ls[p]);
    else if(L>mid) return query(mid+1,r,L,R,rs[p]);
    else return query(l,mid,L,R,ls[p])+query(mid+1,r,L,R,rs[p]);
}

bool check(int x){
    int sum=0;
    if(q[2]+1<=q[3]-1) sum+=query(1,n,q[2]+1,q[3]-1,root[x]).sum;
    sum+=query(1,n,q[1],q[2],root[x]).rmx;
    sum+=query(1,n,q[3],q[4],root[x]).lmx;
    return sum>=0?1:0;
}

bool cmp(node a,node b){
    return a.v<b.v;
}

int main(){
    n=read();
    for(int i=1;i<=n;i++) a[i]=node(read(),i);
    sort(a+1,a+1+n,cmp);
    build(1,n,root[1]);
    for(int i=2;i<=n;i++) root[i]=root[i-1],modify(1,n,a[i-1].pos,root[i]);
    m=read();
    for(int i=1;i<=m;i++){
        for(int i=1;i<=4;i++) q[i]=read();
        for(int i=1;i<=4;i++) q[i]=(q[i]+ans)%n+1;
        sort(q+1,q+5);
        int l=1,r=n,o;
        while(l<=r){
            int mid=(l+r)>>1;
            if(check(mid)) o=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",ans=a[o].v);
    }
    return 0;
}

https://www.cnblogs.com/luoyj/p/13750090.html

posted @ 2020-11-01 13:40  ke_xin  阅读(39)  评论(0编辑  收藏  举报