席树发明者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
#include
#include
#include
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>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 */
这里的这棵树每个节点都可以想象成一颗线段树,维护的是这个点到root的信息。那么很明显,对于第x个节点就可以利用x的fa那棵线段树的信息。
对于,每一次的询问我们去找x->y的路径是怎么处理呢?? Lca求出最近公共祖先
设这个最近公共祖先为nearfa,那x—>y的路径的信息就可以推出来了:root->x的信息+root->y的信息-root->nearfa的信息-root->nearfa.fa的信息.
我的lca不太熟,所以调了很久。RE到死。。。
具体代码:
#include
#include
#include
#include
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=0;i--)
if(t[x].dep-t[y].dep>=(1<=0;i--)
{
if(t[x].dep>=(1<
带修改的的主席树
思路:我们先将原值建成主席树,对于那些修改,我们再建n棵线段树(这里的线段树可以利用root[0]那棵线段树的信息,反正都是空的),每棵树维护的范围(i-lowbit(i)+1,i),两两间没有联系,用树状数组维护。对于那些询问,
我们一切照旧,记得要用原值+修改值即可。
如果是零基础的话,看这段话,可能不好理解。
推荐一篇blog:http://www.cnblogs.com/Empress/p/4659824.html
看完这篇再看一下代码,就差不多了。
然而我wang到死,对拍对了30min连个pi都没放出来。。。。。
贴个标程:
#include
#include
#include
#include
#include
#include
#include
#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次改变
这里面我们并不是按照每一位为根去维护1-i的范围,而是每次修改都建一棵线段树维护所有范围,每次修改都可以利用前一次的来建树。
对于C操作,我们可以打一个lazy标记,跟线段树一样。#include
#include
#include
#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(lmid)
{
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();
}
}