ZOJ 2112
题目大意:给n个数,有m个操作。修改某个数,或者询问一段区间的第k小值。
动态主席树的意思就是原来的数组已经建好主席树了,然后又要修改数组中的某个值,然后还有许多查询,当然不止一次修改。
如果我们每次都建立主席树的话假如有n个数m次修改就是n*m的时间复杂度,这在n,m很大的时候就不行了,于是我们就新引进树状数组来存数组的变化值,当每次查询的时候只要将原来主席树里的值算出来,加上变化量就是当前的值了。
具体的树状数组怎么实现呢?
普通的修改一个值的树状数组相信很多人都会吧,不会的自行去百度在这里不做过多的解释。主席树就相当是一个前缀,所以可以相减解决区间问题,只不过这里的每个点代表的是一颗线段树。所以我们可以在这里可以用树状数组更新,存储变化值,每个点的修改都是一棵线段树,比如将第2个数进行修改,本来是2现在改成5,树状数组本来就会在相应的点+3,而现在是线段树,就可以现在代表2的位置-1再在代表5的位置+1这样就完成了修改,这是每个单点的修改,其他的就用树状数组进行对应的修改就好了,查询的时候也是用树状数组的查询方式加上原来主席树里的值就好了。
#include
#include
#include
#include
#define LL long long
using namespace std;
const int maxn=10000*15*16;
/*
数组注解:
root:代表主席树的每个数字对应点的节点编号
a:原数组的数
hash:离散化是二分的数组
root2:树状数组里线段树的根节点编号
rootl:每次查找的多个树状数组左区间节点编号
rootr:每次查找的多个树状数组右区间节点编号
*/
int a[50010],Hash[60010],root[50010],sz,n,m;
int root2[50010],rootl[50],rootr[50];
int cntl,cntr;//每次更新时左右区间树状数组需要查找的最多节点数
struct Query//存储操作
{
int opt,i,j,k;
} q[10010];
int get_hash(int x)//hash值
{
return lower_bound(Hash+1,Hash+sz,x)-Hash;
}
struct chairman_of_tree
{
struct zp
{
int l,r,sum;
void clear()
{
l=r=sum=0;
}
} tree[maxn];
int cont;
void init()
{
sz=n+1;
memset(root2,0,sizeof(root2));
cont=1;
tree[0].clear();
}
void update(int pre,int &k,int l,int r,int num,int val)//建立静态主席树
{
tree[cont]=tree[pre],tree[cont].sum+=val,k=cont++;
if(l==r) return ;
int mid=(l+r)>>1;
if(lif(num<=mid)
update(tree[pre].l,tree[k].l,l,mid,num,val);
else
update(tree[pre].r,tree[k].r,mid+1,r,num,val);
}
}
int lowbit(int x)
{
return x&(-x);
}
void arr_update(int &rt,int l,int r,int pos,int val)//将第k个数改为val,更新树状数组
{
if(rt==0)
{
rt=cont++;
tree[rt].clear();
}
tree[rt].sum+=val;
if(l==r) return ;
int mid=(l+r)>>1;
if(pos<=mid) arr_update(tree[rt].l,l,mid,pos,val);
else arr_update(tree[rt].r,mid+1,r,pos,val);
}
void arr_update(int k,int val)//将第k个数改为val预处理
{
int x=get_hash(a[k]),y=get_hash(val);
a[k]=val;
while(k<=n)
{
arr_update(root2[k],1,sz,x,-1);//从树状数组里先减去原值
arr_update(root2[k],1,sz,y,1);//给新值加上1
k+=lowbit(k);//树状数组原理更新每一个线段树
}
}
int query(int ql,int qr,int l,int r,int k)//查询l~r的区间第k个数
{
if(l==r) return l;
int sum=tree[tree[qr].l].sum-tree[tree[ql].l].sum,mid=(l+r)>>1;//先求出主席树的两个节点的差值
for(int i=0; i//加上树状数组里右区间的前缀和,代表右区间以前的变化量
for(int i=0; i//同上代表左区间的变化量,注意是减去,这才是本区间的变化量
//sum代表的就是现在区间内的值是多少
if(sum>=k)
{
for(int i=0; i//树状数组所有的树节点统一向左走
for(int i=0; ireturn query(tree[ql].l,tree[qr].l,l,mid,k);
}
else
{
for(int i=0; i//同上向右走
for(int i=0; ireturn query(tree[ql].r,tree[qr].r,mid+1,r,k-sum);
}
}
int kth(int l,int r,int k)//查询l~r的区间第k个数预处理
{
cntl=cntr=0;//把树状数组里面会影响到本次查询的修改都存在数组里面
for(int i=l-1; i; i-=lowbit(i)) rootl[cntl++]=root2[i];//左区间
for(int i=r; i; i-=lowbit(i)) rootr[cntr++]=root2[i];//右区间
return Hash[query(root[l-1],root[r],1,sz,k)];
}
} ac;
int main()
{
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
scanf("%d%d",&n,&m);
ac.init();
for(int i=1; i<=n; i++)
scanf("%d",&a[i]),Hash[i]=a[i];
char c;
for(int i=0; i<m; i++)
{
scanf(" %c",&c);
if(c=='Q')
{
q[i].opt=0;
scanf("%d%d%d",&q[i].i,&q[i].j,&q[i].k);
}
else
{
q[i].opt=1;
scanf("%d%d",&q[i].i,&q[i].j);
Hash[sz++]=q[i].j;
}
}
sort(Hash+1,Hash+sz);//离散化
for(int i=1; i<=n; i++) //建立主席树
ac.update(root[i-1],root[i],1,sz,get_hash(a[i]),1);
for(int i=0; i<m; i++)
{
if(q[i].opt==0)
printf("%d\n",ac.kth(q[i].i,q[i].j,q[i].k));
else
ac.arr_update(q[i].i,q[i].j);
}
}
}