主席树

梦寐中的主席树!啊!原来好简单!(带修其实好难理解

大致知识

普通主席树

  1. 权值线段树
  2. 离散化
  3. 每加一个点新建一条根到叶的路实现用NlogN空间存N个权值线段树
  4. 查询[x,y],用第y棵树与第 x − 1 x-1 x1棵树做差

带修主席树

  1. 用普通主席树维护原序列
  2. 用树状数组套主席树保存修改,即树状数组第i点是区间 [ i − l o w b i t ( i ) + 1 , i ] [i-lowbit(i)+1,i] [ilowbit(i)+1,i]的权值线段树
  3. 修改就是树状数组上权值线段树的修改。时间复杂度就是 ( log ⁡ n ) 2 (\log n)^2 (logn)2(树状数组修改*权值线段树修改)
  4. 查询 [ x , y ] [x,y] [x,y],原主席树用第y棵树与第 x − 1 x-1 x1棵树做差,树状数组则用y前缀和减去x-1的前缀和,再综合起来在权值线段树查询 (具体看分析&代码

完事~

Problem List

分析 & 代码

求区间第k小值。

//Luo P3834
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
struct ooo{
    int l,r,sum;
}e[maxn*50];
int a[maxn],root[maxn],cnt=0;
vector<int> v;
int getId(int x)
{
    return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void update(int l,int r,int &x,int y,const int &pos)
{
    x=++cnt;
    e[x]=e[y];
    e[x].sum++;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pos<=mid)update(l,mid,e[x].l,e[y].l,pos);
    else update(mid+1,r,e[x].r,e[y].r,pos);
}
int query(int l,int r,int x,int y,int k)
{
    if(l==r)return l;
    int mid=(l+r)>>1;
    int s=e[e[y].l].sum-e[e[x].l].sum;
    if(k<=s)return query(l,mid,e[x].l,e[y].l,k);
    else return query(mid+1,r,e[x].r,e[y].r,k-s);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    v.resize(n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",a+i);
        v[i]=a[i];
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
    for(int i=0;i<n;i++) update(1,n,root[i+1],root[i],getId(a[i]));
    int x,y,k;
    while(m--)
    {
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);
    }
}

求区间内≤k的数的个数

//HDU 4417
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
inline void read(int &x)
{
	x=0;
	char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9') { x=(x<<1)+(x<<3)+c-'0'; c=getchar(); }
}
struct ooo{
	int l,r,sum;
}tree[maxn*50];
int cnt=0,root[maxn];
vector<int> a,v;
int getId(const int &x)
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1; 
}
void update(int l,int r,int &x,int y,const int &pos)
{
	x=++cnt;
	tree[x]=tree[y];
	tree[x].sum++;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(pos<=mid)update(l,mid,tree[x].l,tree[y].l,pos);
	else update(mid+1,r,tree[x].r,tree[y].r,pos);
}
int query(int l,int r,int x,int y,int k)//查询区间内位置比k小的数量 
{
	if(r<k)return tree[y].sum-tree[x].sum;
	else if(l>=k)return 0;
	int mid=(l+r)>>1;
	if(k<=mid)return query(l,mid,tree[x].l,tree[y].l,k);
	else return tree[tree[y].l].sum-tree[tree[x].l].sum+query(mid+1,r,tree[x].r,tree[y].r,k);	
}
int main()
{
	int n,m,T;
	read(T);
	for(int casei=1;casei<=T;casei++)
	{
		printf("Case %d:\n",casei);
		read(n),read(m);
		v.resize(n);
		for(int i=0;i<n;i++) read(v[i]);
		a=v;
		sort(v.begin(),v.end());
		v.erase(unique(v.begin(),v.end()),v.end());//离散化
		for(int i=0;i<n;i++) update(1,n,root[i+1],root[i],getId(a[i]));
		int x,y,k;
		while(m--)
		{
			read(x),read(y),read(k);
			k=upper_bound(v.begin(),v.end(),k)-v.begin()+1;//比k大的第一个数在树上的ID
			printf("%d\n",query(1,n,root[x],root[y+1],k));
		}
	}
}

求树上两点之间的边权≤k的边数。树上建主席树+LCA。所谓主席树即可持续化线段树,给序列建主席树->每增加一个数在上一个数的版本上更新版本,而给树建主席树->每个节点在其父节点的版本上更新版本。

//2019ICPC南昌邀请赛网络赛J题 Distance on the tree
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct Node{
	int l,r,sum;
}tree[maxn*50];
struct Edge{
	int to,value;
};
int dep[maxn],fa[maxn][21],root[maxn],n;
vector<Edge> G[maxn];
vector<int> v;
int getId(const int &x)
{
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
void update(int l,int r,int &x,int y,int pos)
{
	static int cnt=0;
	x=++cnt;
	tree[x]=tree[y];
	tree[x].sum++;
	if(l==r)return;
	int mid=(l+r)>>1;
	if(pos<=mid)update(l,mid,tree[x].l,tree[y].l,pos);
	else update(mid+1,r,tree[x].r,tree[y].r,pos);
}
int query(int l,int r,int x,int y,int pos)//查询(x,y]中比pos小的个数 
{
	if(pos>r)return tree[y].sum-tree[x].sum;
	if(pos<=l)return 0;
	int mid=(l+r)>>1;
	if(pos<=mid)return query(l,mid,tree[x].l,tree[y].l,pos);
	int s=tree[tree[y].l].sum-tree[tree[x].l].sum;
	return s+query(mid+1,r,tree[x].r,tree[y].r,pos);
}
void dfs(int u)
{
	for(int i=1;(1<<i)<=dep[u];i++) fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=0;i<G[u].size();i++)
	{
		Edge &e=G[u][i];
		if(e.to==fa[u][0])continue;
		fa[e.to][0]=u;
		dep[e.to]=dep[u]+1;
		update(1,n,root[e.to],root[u],getId(e.value));
		dfs(e.to);
	}
}
int lca(int x,int y)
{
	if(dep[x]>dep[y])swap(x,y);
	int f=dep[y]-dep[x]; 
	for(int i=0;(1<<i)<=f;i++)
		if((1<<i)&f)y=fa[y][i];
	if(x==y)return x;
	for(int i=19;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}
int main()
{
	int m,x,y,k;
	scanf("%d%d",&n,&m);
	//v.resize(n);
	for(int i=0;i<n-1;i++)
	{
		scanf("%d%d%d",&x,&y,&k);
		G[x].push_back({y,k});
		G[y].push_back({x,k});
		v.push_back(k); 
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	dfs(1);
	for(int i=0;i<m;i++)
	{
		scanf("%d%d%d",&x,&y,&k);
		k=upper_bound(v.begin(),v.end(),k)-v.begin()+1;
		int LCA=lca(x,y);
		printf("%d\n",query(1,n,root[LCA],root[x],k)+query(1,n,root[LCA],root[y],k));
		//printf("%d\n",query(1,n,root[1],root[x],k)+query(1,n,root[1],root[y],k)-2*query(1,n,root[1],root[LCA],k));
		//网上写法,感觉跟我一样,都AC了
	}
}
/*
6 99
1 3 5
2 6 2
2 4 7
5 3 3
2 1 10
*/

带修主席树,个人理解主要是用到了主席树和树状数组的特点巧妙使复杂度变成 O ( n ( log ⁡ n ) 2 ) O(n(\log n)^2) O(n(logn)2)

//带修主席树 Dynamic Rankings
#include<bits/stdc++.h>
#define read(xxx) scanf("%d",&xxx)
#define lowbit(xx) (xx&(-xx))
#define write(xxx) printf("%d\n",xxx)
using namespace std;
const int maxn=5e4+5,maxm=1e4+5;
struct node{
	int l,r,k,op;
}q[maxm];//保存查询或修改
struct ooo{
	int l,r,sum;
}tree[(maxn+maxm)*32];
int n,m,sz,root[maxn],b[maxn+maxm],a[maxn],s[maxn+maxm],rl[200],rr[200];
//a为此时的序列,b为离散化后,s存树状数组,即s[i]为[i-lowbit(i)+1,i]的修改情况
//root为原序列主席树根,rl,rr临时存放修改节点的主席树根节点
int getId(int x){ return lower_bound(b,b+sz,x)-b+1; }
int cnt=0,cntl,cntr;
struct Tree{
	void update(int l,int r,int &x,int y,int pos)//普通主席树更新
	{
		x=++cnt;
		tree[x]=tree[y];
		tree[x].sum++;
		if(l==r)return;
		int mid=(l+r)>>1;
		if(pos<=mid)update(l,mid,tree[x].l,tree[y].l,pos);
		else update(mid+1,r,tree[x].r,tree[y].r,pos); 
	}
	void add_update(int l,int r,int &x,int pos,int val)//修改
	{
		if(x==0)
		{
			x=++cnt;
			tree[x].sum=tree[x].l=tree[x].r=0;
		}
		tree[x].sum+=val;
		if(l==r)return;
		int mid=(l+r)>>1;
		if(pos<=mid)add_update(l,mid,tree[x].l,pos,val);
		else add_update(mid+1,r,tree[x].r,pos,val);
	}
	void add(int idx,int val)
	{
		int x=getId(a[idx]),y=getId(val);
		a[idx]=val;
		while(idx<=n)
		{
			add_update(1,sz,s[idx],x,-1);
			add_update(1,sz,s[idx],y,1);
			idx+=lowbit(idx);
		}
	}
	int query(int l,int r,int x,int y,int k)
	{
		if(l==r)return l;
		int sum=tree[tree[y].l].sum-tree[tree[x].l].sum;
		for(int i=1;i<=cntl;++i) sum-=tree[tree[rl[i]].l].sum;
		for(int i=1;i<=cntr;++i) sum+=tree[tree[rr[i]].l].sum;
		int mid=(l+r)>>1;
		if(sum>=k)
		{
			for(int i=1;i<=cntl;++i) rl[i]=tree[rl[i]].l;
			for(int i=1;i<=cntr;++i) rr[i]=tree[rr[i]].l;
			return query(l,mid,tree[x].l,tree[y].l,k);
		}
		else
		{
			for(int i=1;i<=cntl;++i) rl[i]=tree[rl[i]].r;
			for(int i=1;i<=cntr;++i) rr[i]=tree[rr[i]].r;
			return query(mid+1,r,tree[x].r,tree[y].r,k-sum);
		}
	}
	int kth(int l,int r,int k)
	{
		cntl=cntr=0;
		for(int i=l-1;i;i-=lowbit(i))
		    rl[++cntl]=s[i];  //l-1的前缀和所包含的节点
		for(int i=r;i;i-=lowbit(i))
		    rr[++cntr]=s[i];  //r的前缀和所包含的节点
		int ans=query(1,sz,root[l-1],root[r],k);
		return b[ans-1];
	}
	
}tr;
void init()
{
	memset(s,0,sizeof(s));
	cnt=0;
	sz=0;
}
int main()
{
	int T;
	read(T);
	while(T--){
		init();
		read(n),read(m);
		for(int i=1;i<=n;i++) read(a[i]),b[sz++]=a[i];
		char tmp;
		for(int i=0;i<m;i++){
			scanf(" %c",&tmp);
			if(tmp=='Q'){
				q[i].op=1;
				read(q[i].l),read(q[i].r),read(q[i].k);
			}
			else{
				q[i].op=2;
				read(q[i].l),read(q[i].k);b[sz++]=q[i].k;
			}
		}
		sort(b,b+sz);
		sz=unique(b,b+sz)-b;
		for(int i=1;i<=n;i++) //原序列建树
		    tr.update(1,sz,root[i],root[i-1],getId(a[i]));
		for(int i=0;i<m;i++)
			if(q[i].op==1) write(tr.kth(q[i].l,q[i].r,q[i].k));
			else tr.add(q[i].l,q[i].k);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值