主席树专题

最近在学主席树所以开个坑。

席树发明者fotile,后人也称其为fotile主席。同时主席树也被叫做可持续化线段树等,因为主席树这个名字显得高贵冷艳,所以现在大部分人都叫它主席树。——引子


无修改的主席树

先看题,

poj2104 K-th number

题目大意:给出n个数,m个询问。下来给出n个数a1—an(这些数的范围<=10^9,是 int类型),再给m组询问,每组询问有三个数分别是l,r,k,表示l到r这个范围内 第k小(这个范围从小到大排序,求第k个)的是多少。
样例1:
Input:                 Output:
7 3                      5
1 5 2 6 3 7 4       6
2 5 3                   3
4 4 1
1 7 3

我们发现tree[i]不过是比Tree[i-1]要多维护一个值。针对这个值,我们再去思考,这个值如果不是要Tree[i]的左孩子管,就是Tree[i]的右孩子管。那不就意味着Tree[i]的左右孩子中的一棵孩子树会和Tree[i-1]的对应孩子树完全一样!

利用这个性质,我们可以很方便的建树。

那么我们可以 以[l,r]区间内的数的个数来建立一棵线段树。

结点的值是数的个数,当我们要找第k小的数时,若左子树大于k,那么很显然第k小的数在左子树中;若左子树小于k,那么第k小的数在右子树

同样的,我们只要建立[1, i] (i是1到n之间的所有值)的所有树,每当询问[l, r]的时候,只要用[1, r]的树减去[1, l-1]的树,再找第k小就好啦

我们将这n个树看成是建立在一个大的线段树里的,也就是这个线段树的每个节点都是一个线段树( ——这就是主席树)

模板:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200010;
int n,m;
struct node{
	int x,id;
}a[N];
struct node1{
	int lc,rc,val;
}tr[N*20];int tot;
bool cmp(node x,node y)
{
	return x.x<y.x;
}
int rank[N];
void read()
{
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i].x);
		a[i].id=i;
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++) rank[a[i].id]=i;
}
int root[N];
void built(int l,int r)
{
	 tot++; 
     int now,mid; now=tot;
     if(l<r)
     {
            mid=(l+r)>>1; 
            tr[now].lc=tot+1; built(l,mid); 
            tr[now].rc=tot+1; built(mid+1,r); 
     }
}
void update(int froot,int l,int r,int k)
{
     tot++; 
     int now; now=tot; 
     tr[now]=tr[froot]; 
     int mid=(l+r)>>1; 
     if(l==r) {tr[now].val++; return ;}
     if(k<=mid)
     {
           tr[now].lc=tot+1; 
           update(tr[froot].lc,l,mid,k);  
     }
     else 
     {
           tr[now].rc=tot+1; 
           update(tr[froot].rc,mid+1,r,k);
     }
     tr[now].val=tr[ tr[now].lc ].val+tr[ tr[now].rc ].val;
}
int sum;
void get_ans(int l,int r,int lroot,int rroot,int k)
{
	 if(l==r) {sum=l; return ;}
     int tmp=tr[ tr[rroot].lc ].val-tr[ tr[lroot].lc ].val;
     int mid=(l+r)>>1;  
     if(k<=tmp) get_ans(l,mid,tr[lroot].lc,tr[rroot].lc,k); 
     else get_ans(mid+1,r,tr[lroot].rc,tr[rroot].rc,k-tmp); 
}
void solve()
{
	tot=0;
	root[0]=tot+1;
	built(1,n);
	for(int i=1;i<=n;i++)
	{
		root[i]=tot+1;
		update(root[i-1],1,n,rank[i]);
	}
	for(int i=1;i<=m;i++)
	{
		int l,r,k;
		scanf("%d%d%d",&l,&r,&k);
		get_ans(1,n,root[l-1],root[r],k);
		//printf("%d\n",sum);
		printf("%d\n",a[sum].x);
	}
}
int main()
{
    scanf("%d%d",&n,&m);
	
	read();
	solve();
}
/*7 3                       
1 5 2 6 3 7 4            
2 5 3
4 4 1       */     

再上一题:bzoj2588

具体题目自己看

这里的这棵树每个节点都可以想象成一颗线段树,维护的是这个点到root的信息。那么很明显,对于第x个节点就可以利用x的fa那棵线段树的信息。

对于,每一次的询问我们去找x->y的路径是怎么处理呢??  Lca求出最近公共祖先

设这个最近公共祖先为nearfa,那x—>y的路径的信息就可以推出来了:root->x的信息+root->y的信息-root->nearfa的信息-root->nearfa.fa的信息.

我的lca不太熟,所以调了很久。RE到死。。。

具体代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110000;
struct node{
    int x,id;
} sa[N];int rank[N];
struct node1{
    int x,y,next;
}ss[N*2];int len=0,first[N];
struct node2{
    int dep,fa[24];
}t[N];
struct node3{
    int lc,rc,via;
}tr[N*20];int root[N],tot;
int n,m;
bool cmp(node x,node y)
{
    return x.x<y.x;
}
void ins(int x,int y)
{
    len++;
    ss[len].x=x;
    ss[len].y=y;
    ss[len].next=first[x];
    first[x]=len;
}
 
void read()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&sa[i].x);
        sa[i].id=i;
    }
    sort(sa+1,sa+1+n,cmp);
    for(int i=1;i<=n;i++) rank[sa[i].id]=i;
    memset(first,-1,sizeof(first));
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ins(x,y);
        ins(y,x);
    }
}
void update(int froot,int l,int r,int k)
{
    tot++;
    int now=tot;
    tr[now]=tr[froot];
    if(l==r){tr[now].via++;return;}
    int mid=(l+r)/2;
    if(k<=mid)
    {
        tr[now].lc=tot+1;
        update(tr[froot].lc,l,mid,k);
    }
    else
    {
        tr[now].rc=tot+1;
        update(tr[froot].rc,mid+1,r,k);
    }
    tr[now].via=tr[tr[now].lc].via+tr[tr[now].rc].via;
}
void bt(int x,int fa)
{
    t[x].dep=t[fa].dep+1;t[x].fa[0]=fa;
    for(int i=1;(1<<i)<=t[x].dep;i++)
    t[x].fa[i]=t[t[x].fa[i-1]].fa[i-1];
    root[x]=tot+1;update(root[fa],1,n,rank[x]);
    for(int i=first[x];i!=-1;i=ss[i].next)
    {
        int y=ss[i].y;
        if(y!=fa) bt(y,x);
    }
}
int lca(int x,int y)
{
    if(t[x].dep<t[y].dep){int tt=x;x=y;y=tt;}
    for(int i=23;i>=0;i--)
    if(t[x].dep-t[y].dep>=(1<<i))
    x=t[x].fa[i];
    if(x==y) return x;
    for(int i=23;i>=0;i--)
    {
        if(t[x].dep>=(1<<i) && t[x].fa[i]!=t[y].fa[i])
        {
            x=t[x].fa[i];
            y=t[y].fa[i];
        }
    }
    return t[x].fa[0];
}
 
void built(int l,int r)
{
    tot++;
    int now=tot;
    tr[now].via=0;
    if(l<r)
    {
        int mid=(l+r)/2;
        tr[now].lc=tot+1;built(l,mid);
        tr[now].rc=tot+1;built(mid+1,r);
    }
}
 
int num;
void get_ans(int l,int r,int lroot,int rroot,int near,int fanear,int k)
{
    if(l==r) {num=l;return;}
    int sum=tr[tr[lroot].lc].via+tr[tr[rroot].lc].via-tr[tr[near].lc].via-tr[tr[fanear].lc].via;
    int mid=(l+r)/2;
    if(k<=sum) get_ans(l,mid,tr[lroot].lc,tr[rroot].lc,tr[near].lc,tr[fanear].lc,k);
    else get_ans(mid+1,r,tr[lroot].rc,tr[rroot].rc,tr[near].rc,tr[fanear].rc,k-sum);
}
void solve()
{
    tot=0;
    root[0]=tot+1;
    built(1,n);
    t[0].dep=0;
    bt(1,0);
     
    int lastans=0;
    int l,r,k;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&l,&r,&k);
        l=l^lastans;
        int nearfa=lca(l,r);
        get_ans(1,n,root[l],root[r],root[nearfa],root[t[nearfa].fa[0]],k);
        lastans=sa[num].x;
        if(i!=m) printf("%d\n",sa[num].x);
        else printf("%d",sa[num].x);
    }
}
int main()
{
    read();
    solve();
}

带修改的的主席树

还在理解中。。。。。
今天理解了一下。

感觉还行。
来看例题,zoj2112
题目大意:
先是一个T,表示下来T组数据。每组数据先是n,m,分别表示n个数和m个操作。
先来先是n个数。再来m组操作,有两种:
(1)给C x y.表示把第x个数改成y
(2)给Q x y k.表示求l-r这个区间内的第k大数
对于每个Q,输出一个答案
其实是想做bzoj1901的,但没权限(穷),于是选了一道较变态的题做模板题。QWQ

思路:我们先将原值建成主席树,对于那些修改,我们再建n棵线段树(这里的线段树可以利用root[0]那棵线段树的信息,反正都是空的),每棵树维护的范围(i-lowbit(i)+1,i),两两间没有联系,用树状数组维护。对于那些询问,

我们一切照旧,记得要用原值+修改值即可。

如果是零基础的话,看这段话,可能不好理解。

推荐一篇blog:http://www.cnblogs.com/Empress/p/4659824.html

看完这篇再看一下代码,就差不多了。

然而我wang到死,对拍对了30min连个pi都没放出来。。。。。

贴个标程:

#include <cmath>
#include <cstdio>
#include <climits>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define mes(a,x) memset(a,x,sizeof(a))
#define lowbit(x) ( x&( -x ) )
#define LL long long 
using namespace std; 
const int maxn=(int)5e4+10;
const int INF=0x7fffffff;  

int num[maxn],vec[maxn*2]; 
struct Query
{
    int x,y,k,flag; 
}Q[maxn]; 

//Fotile tree

struct Fotile_tree
{
    int lc,rc,c; 
}tr[maxn*50]; int tot,root[maxn]; 

int n,m,idx,cnt,Right,Left,T; 

void Build(int l,int r)
{
    int now=++tot;
    tr[now].c=0; 
    if(l < r)
    {
        int mid=(l+r)>>1; 
        tr[now].lc=tot+1; Build(l,mid); 
        tr[now].rc=tot+1; Build(mid+1,r); 
    } 
}

void Update(int froot,int l,int r,int pos,int val)
{
    int now=++tot; 
    tr[now]=tr[froot]; 
     
    if(l==r) {tr[now].c+=val; return ;}
    int mid=(l+r)>>1; 
     
    if(pos<=mid) tr[now].lc=tot+1,Update( tr[froot].lc , l , mid , pos , val ); 
    else tr[now].rc=tot+1,Update( tr[froot].rc , mid+1 , r , pos , val ); 
  
    tr[now].c=tr[ tr[now].lc ].c+tr[ tr[now].rc ].c;  
    //Update无变化~ 
}

//BIT
int s[maxn],use[maxn]; 

void Change(int k,int pos,int val)
{
	while(k<=n)
	{
		int past=s[k];
		s[k]=tot+1; 
		Update(past,1,idx,pos,val); 
		k+=lowbit(k);
	}
}

int Getsum(int pos)
{
	int res=0; 
	while(pos>=1)
	{
		res+=tr[ tr[ use[pos] ].lc ].c; //修改值中左边的有几个 
		pos-=lowbit(pos); 
	}
	return res; 
}

void Query(int lroot,int rroot,int l,int r,int pos)
{
	int i,j;  
	int tmp= Getsum(Right)-Getsum(Left)+tr[ tr[rroot].lc ].c-tr[ tr[lroot].lc ].c ; 
	//修改后,归左边有几个 
	int mid=(l+r)>>1; 
	if(l==r) {cnt=l; return ;}
	if(pos<=tmp)
	{
		for(i=Left;i>0;i-=lowbit(i)) use[i]=tr[ use[i] ].lc; 
		for(i=Right;i>0;i-=lowbit(i)) use[i]=tr[ use[i] ].lc; 
		Query(tr[lroot].lc,tr[rroot].lc,l,mid,pos); 
	}
	else
	{
		for(i=Left;i>0;i-=lowbit(i)) use[i]=tr[ use[i] ].rc; 
		for(i=Right;i>0;i-=lowbit(i)) use[i]=tr[ use[i] ].rc; 
		Query(tr[lroot].rc,tr[rroot].rc,mid+1,r,pos-tmp); 
	}
	
}

void Read()
{
	int i,j; 
	char op[5]; 
	idx=0; 
	scanf("%d%d",&n,&m); 
	for(i=1;i<=n;i++)
	{
		scanf("%d",&num[i]); 
		vec[++idx]=num[i];
	}
	
	for(i=1;i<=m;i++)
	{
		scanf("%s",op); 
		if( op[0]=='C' )
		{
			scanf("%d%d",&Q[i].x,&Q[i].y); 
			Q[i].flag=0; 
			vec[++idx]=Q[i].y; 
		}
		else 
		{
			scanf("%d%d%d",&Q[i].x,&Q[i].y,&Q[i].k); 
			Q[i].flag=1; 
		}
	}
	//读入只需要标记一下询问和修改 
	
}
void Solve()
{
	int i,j; 
	tot=0; 
	sort(vec+1,vec+1+idx);
	
	idx=unique(vec+1,vec+1+idx)-(vec+1); 
	root[0]=tot+1; Build(1,idx); 
	//将要出现和已出现的都存起来 ,先建空树 
	for(i=1;i<=n;i++)
	{
		int pos=lower_bound(vec+1,vec+1+idx,num[i])-vec; 
		root[i]=tot+1; 
		Update(root[i-1],1,idx,pos,1); 
	}
	//和无修改的一样,已经出现每个都去建一棵新树 
	
	for(i=0;i<=n;i++) s[i]=root[0]; 
	//表示第i个数所在的那棵树的根,为什么不是root[i],因为这里存的是修改值 
	
	for(i=1;i<=m;i++)
	{
		if(Q[i].flag==0)
		{
			int pos1,pos2; 
			pos1=lower_bound(vec+1,vec+1+idx,num[ Q[i].x ] ) - vec ; 
			pos2=lower_bound(vec+1,vec+1+idx, Q[i].y  ) - vec ; 
			//lower_bound的作用可以理解成求这个数的离散值
			//其实官方说法不是这样的~ 
			Change( Q[i].x , pos1 , -1 );
			Change( Q[i].x , pos2 , 1 ); 
			//change分两步,先将原值抹杀,再添上新值 
			num[ Q[i].x ]=Q[i].y; 		
			//修改原数组的值 
		}
		
		else
		{ 
            cnt=0; 
            Left=Q[i].x-1; Right=Q[i].y; 
            //先处理好左右边界 
            for(j=Left;j>=1;j-=lowbit(j)) use[j]=s[j]; 
            for(j=Right;j>=1;j-=lowbit(j)) use[j]=s[j]; 
            //use数组是表示用到的根节点,它会不断改变的 
			Query(  root[Q[i].x-1]  ,  root[Q[i].y]  , 1 , idx, Q[i].k ); 
			printf("%d\n", vec[ cnt ] ); //vec数组排序后的cnt位 
		}
	}		
	
}

int main()
{
    //freopen( "r.in" , "r" , stdin );
    //freopen( "w.out" , "w" , stdout );
    scanf("%d",&T); 
    while(T--)
    {
        Read(); 
        Solve(); 
    }
    return 0; 
}
/*
对于修改,第一次修改依附root[0]建树,后面就是依附前一次的那棵树再建树 
对于询问,记得带上修改值

模拟一组数据
1
3 2
1 2 3
C 2 4
Q 1 3 3

模拟他询问的过程:
问1-3这个区间第3大
Getsum(Right)=-1,因为你在修改的时候将2抹掉了,节点的值标为-1
tr[ tr[rroot].lc ].c=2,其余为0
进入右孩子,找第2小!!!!!(相当于删去了2,在3,4中找第2小)
tr[ ttr[rroot].lc ] =1,其余为0
进入右孩子,就找到cnt=4 
*/


来一道访问历史版本的。。

看到题的时候开始怀疑人生了。

题目大意:

spoj to the moon:
多组数据,先是两个整数n,m.表示下来n个数,m个操作。每个数都<=10^9.操作分为4种:
1.Q l r 表示问现在l—r这个范围内所有数的和
2.C l r d 表示l—r这个范围内每个数都加上d
3.H l r t 表示第t次改变时,l—r这个范围内所有数的和
4.B t 表示时间返回到第t次改变的时候,t以后的改变全部作废


注意:没改变时,为第0次改变


后来看了看题解大致懂了QWQ

这里面我们并不是按照每一位为根去维护1-i的范围,而是每次修改都建一棵线段树维护所有范围,每次修改都可以利用前一次的来建树。

对于C操作,我们可以打一个lazy标记,跟线段树一样。
对于历史版本,我们可以用一个mark来标记一下,当我们建一颗新树时,如果一段区间的l!=r那么它就是历史版本,那么下一次操作时,我们不能动它,而是重新建两颗子树来维护它。大概就是这样
代码如下:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define ll long long
const int N=( int )1e5+100; 
int n,m;
ll num[N];
struct node{
	int lc,rc,lazy,mark;
	ll c;
}tr[N*100];int tot,root[N<<1];
void read()
{
	for(int i=1;i<=n;i++)
	scanf("%lld",&num[i]);
}
void pushup(int root)
{
	tr[root].c=tr[ tr[root].lc ].c+tr[ tr[root].rc ].c; 
}
void pushdown(int x,int l,int r)
{
	if(tr[x].mark)
	{
		int now;
		tot++;
		now=tot;
		tr[now]=tr[tr[x].lc];
		tr[x].lc=now;
		tot++;now=tot;
		tr[now]=tr[tr[x].rc];
		tr[x].rc=now;
		tr[tr[x].lc].mark=tr[tr[x].rc].mark=1;
		tr[x].mark=0;
	}
	if(tr[x].lazy)
	{
		int mid=(l+r)/2;
		tr[tr[x].lc].lazy+=tr[x].lazy;
		tr[tr[x].lc].c+=(ll)(mid-l+1)*tr[x].lazy;
		tr[tr[x].rc].lazy+=tr[x].lazy;
		tr[tr[x].rc].c+=(ll)(r-mid)*tr[x].lazy;
		tr[x].lazy=0;
	}
	
}
void built(int l,int r)
{
	tot++;
	int now=tot;
	tr[now].lc=tr[now].rc=-1;
	tr[now].c=tr[now].lazy=0;tr[now].mark=0;
	if(l==r) {
		tr[now].c=num[l];return;
	}
	if(l<r)
	{
		int mid=(l+r)/2;
		tr[now].lc=tot+1;built(l,mid);
		tr[now].rc=tot+1;built(mid+1,r);
	}
    pushup(now);
}

void update(int froot,int l,int r,int ql,int qr,int d)
{
	int now=++tot;
	if(l==ql&&r==qr)
	{
		tr[now].lazy=tr[froot].lazy+d;
		tr[now].c=tr[froot].c+(ll)((r-l+1)*d);
		tr[now].lc=tr[froot].lc;
		tr[now].rc=tr[froot].rc;
		tr[now].mark=(ql!=qr);
		return;
	}
	pushdown(froot,l,r);
	tr[now]=tr[froot];
	int mid=(l+r)/2;
	if(qr<=mid) 
	{
		tr[now].lc=tot+1;
		update(tr[froot].lc,l,mid,ql,qr,d);
	}
	else if(ql>mid)
	{
		tr[now].rc=tot+1;
		update(tr[froot].rc,mid+1,r,ql,qr,d);
	}
	else
	{
		tr[now].lc=tot+1;
		update(tr[froot].lc,l,mid,ql,mid,d);
		tr[now].rc=tot+1;
		update(tr[froot].rc,mid+1,r,mid+1,qr,d);
	}
	pushup(now); 
}
ll get_ans(int now,int l,int r,int ql,int qr)
{
	if(l==ql&&r==qr) return tr[now].c;
	pushdown(now,l,r);
	int mid=(l+r)/2;
	if(qr<=mid) return get_ans(tr[now].lc,l,mid,ql,qr);
	else if(ql>mid) return get_ans(tr[now].rc,mid+1,r,ql,qr);
	else return get_ans(tr[now].lc,l,mid,ql,mid)+get_ans(tr[now].rc,mid+1,r,mid+1,qr);
}
void solve()
{
	tot=0;
	root[0]=tot+1;
	built(1,n);
	int now=0;
	char a[8];
	for(int i=1;i<=m;i++)
	{
		scanf("%s",a);
		if(a[0]=='C')
		{
			int l,r,d;
			scanf("%d%d%d",&l,&r,&d);
			root[now+1]=tot+1;
			update(root[now],1,n,l,r,d);
			now++;
		}
		if(a[0]=='Q')
		{
			int l,r;
			scanf("%d%d",&l,&r);
			printf("%lld\n",get_ans(root[now],1,n,l,r));
		}
		if(a[0]=='H')
		{
			int l,r,t;
			scanf("%d%d%d",&l,&r,&t);
			printf("%lld\n",get_ans(root[t],1,n,l,r));
		}
		if(a[0]=='B')
		{
			int t;scanf("%d",&t);
			now=t;
		}
	}
}
int main()
{
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		read();
		solve();
	}
}


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值