题意:给出长度为n的数组,有两种操作
1、询问区间【L,R】第k大的数的权值
2、将第i个数修改为t
题解:
①第一种方法是树状数组套主席树,要建立关于原数组的主席树和关于修改的树状数组套主席树,两个主席树都以权值建树,由于某个点改变所以影响到很多区间,而树状数组的每一个点都可以代表一个区间,所以我们通过树状数组logn查找相应需要修改的所有的区间,再通过logn来修改相应区间上点的权值,若每次修改都在树状数组上面新建一颗线段树则会m*n*logn爆空间,所以我们通过主席树动态加点,所以询问操作的总共需要保存的空间为m*logn,然后关于原数组的主席树空间为n*logn,所以总的
空间复杂度为(n+m)*logn,然后由于新建一个关于原数组的主席树的时间复杂度为n*logn,修改操作总的复杂度为m*logn*logn所以总的时间复杂度为m*logn*logn。
树状数组套主席树AC代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
using namespace std;
#define N 50005
int n,m;
int a[N],la[N];
int b[N*2],top;
int q[N][4];
map<int,int>mp;
int tree[N*40],lchild[N*40],rchild[N*40];
int root[N],tot;
int c[N],use[N];
void update(int last,int cur,int L,int R,int x,int k)
{
tree[cur]=tree[last]+k;
lchild[cur]=lchild[last];
rchild[cur]=rchild[last];
if(L==R)return ;
int mid=L+R>>1;
if(x<=mid)update(lchild[last],lchild[cur]=++tot,L,mid,x,k);
else update(rchild[last],rchild[cur]=++tot,mid+1,R,x,k);
}
int lowbit(int i)
{
return i&(-i);
}
void add(int i,int v,int k)
{
while(i<=n)
{
int last=c[i];
update(last,c[i]=++tot,1,top,mp[v],k);
i+=lowbit(i);
}
}
int sum(int i)
{
int ans=0;
while(i)
{
ans+=tree[lchild[use[i]]];
i-=lowbit(i);
}
return ans;
}
int query(int l,int r,int k)
{
for(int i=l-1;i;i-=lowbit(i))use[i]=c[i];
for(int i=r;i;i-=lowbit(i))use[i]=c[i];
int L=1,R=top;
int last=root[l-1],cur=root[r];
while(1)
{
if(L==R)return b[L-1];
int mid=L+R>>1;
int lsum=tree[lchild[cur]]-tree[lchild[last]]+sum(r)-sum(l-1);
if(lsum>=k)
{
R=mid;
for(int i=l-1;i;i-=lowbit(i))use[i]=lchild[use[i]];
for(int i=r;i;i-=lowbit(i))use[i]=lchild[use[i]];
last=lchild[last];
cur=lchild[cur];
}
else
{
k-=lsum;
L=mid+1;
for(int i=l-1;i;i-=lowbit(i))use[i]=rchild[use[i]];
for(int i=r;i;i-=lowbit(i))use[i]=rchild[use[i]];
last=rchild[last];
cur=rchild[cur];
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
tot=top=0;
mp.clear();
memset(c,0,sizeof(c));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[top++]=a[i];
la[i]=a[i];
}
for(int i=0;i<m;i++)
{
char op[2];
scanf("%s",op);
if(op[0]=='Q')
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
q[i][0]=0;
q[i][1]=l;
q[i][2]=r;
q[i][3]=k;
}
else
{
int pos,w;
scanf("%d%d",&pos,&w);
q[i][0]=1;
q[i][1]=pos;
q[i][2]=w;
b[top++]=w;
}
}
sort(b,b+top);
top=unique(b,b+top)-b;
for(int i=0;i<top;i++)
mp[b[i]]=i+1;
for(int i=1;i<=n;i++)
update(root[i-1],root[i]=++tot,1,top,mp[a[i]],1);
for(int i=0;i<m;i++)
{
if(q[i][0])
{
add(q[i][1],la[q[i][1]],-1);
add(q[i][1],q[i][2],1);
la[q[i][1]]=q[i][2];
}
else printf("%d\n",query(q[i][1],q[i][2],q[i][3]));
}
}
}
②第二种方法是CDQ分治中的整体二分
每次关于结果,也就是第k个权值为多少进行二分,通过树状数组维护每一次二分时权值小于x的点的个数,将询问分为两种,权值小于x的总点数小于等于k,第二种是总数大于k,然后通过归并排序,分为权值满足【L,mid】的部分和【mid+1,R】的部分继续递归下去。
由于CDQ分治总共logn次递归,每次递归查询和修改的总复杂度为n*logn所以总的时间复杂度为n*logn*logn,空间复杂度为nlogn。
整体二分AC代码:
#include<stdio.h>
#include<algorithm>
#include<map>
#include<string.h>
#define N 50005
using namespace std;
struct node
{
int op,l,pos,k;
int t;
int add;
int id;
}a[N*2],newa[N*2];
int t,top,tot;
int b[N*2];
int pos[N];
int c[N];
int cum[N],mark[N],ANS[N];
map<int,int>mp;
int lowbit(int i)
{
return i&(-i);
}
void change(int i,int k)
{
while(i<=top+1)
{
c[i]+=k;
i+=lowbit(i);
}
}
int sum(int i)
{
int ans=0;
while(i)
{
ans+=c[i];
i-=lowbit(i);
}
return ans;
}
void solve(int posl,int posr,int l,int r)
{
if(l==r)
{
for(int i=posl;i<=posr;i++)
if(a[i].op==3)
ANS[a[i].id]=l;
return ;
}
int mid=(l+r)/2;
for(int i=posl;i<=posr;i++)
{
if(a[i].op==1&&a[i].add<=mid)
change(a[i].pos,1);
if(a[i].op==2&&a[i].add<=mid)
change(a[i].pos,-1);
if(a[i].op==3)
{
mark[a[i].id]=sum(a[i].pos)-sum(a[i].l-1);
cum[a[i].id]+=mark[a[i].id];
}
}
for(int i=posl;i<=posr;i++)
{
if(a[i].op==1&&a[i].add<=mid)
change(a[i].pos,-1);
if(a[i].op==2&&a[i].add<=mid)
change(a[i].pos,1);
}
int l1=posl;
for(int i=posl;i<=posr;i++)
{
if((a[i].op==1||a[i].op==2)&&a[i].add<=mid)
newa[l1++]=a[i];
else if(a[i].op==3&&cum[a[i].id]>=a[i].k)
newa[l1++]=a[i];
}
int l2=l1;
for(int i=posl;i<=posr;i++)
{
if((a[i].op==1||a[i].op==2)&&a[i].add>mid)
newa[l2++]=a[i];
else if(a[i].op==3&&cum[a[i].id]<a[i].k)
{
mark[a[i].id]=0;
newa[l2++]=a[i];
}
}
for(int i=posl;i<=posr;i++)
{
a[i]=newa[i];
if(a[i].op==3)
cum[a[i].id]-=mark[a[i].id];
}
solve(posl,l1-1,l,mid);
solve(l1,posr,mid+1,r);
}
void init()
{
tot=top=t=0;
memset(ANS,-1,sizeof(ANS));
memset(cum,0,sizeof(cum));
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
mp.clear();
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int k;
scanf("%d",&k);
a[++tot].op=1;
a[tot].pos=i;
a[tot].t=++t;
a[tot].add=k;
b[top++]=k;
pos[i]=k;
}
for(int i=1;i<=m;i++)
{
char op[2];
scanf("%s",op);
if(op[0]=='Q')
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
a[++tot].op=3;
a[tot].l=l;
a[tot].pos=r;
a[tot].k=k;
a[tot].t=++t;
a[tot].id=i;
}
else
{
int r,val;
scanf("%d%d",&r,&val);
a[++tot].op=2;
a[tot].pos=r;
a[tot].t=++t;
a[tot].add=pos[r];
a[++tot].op=1;
a[tot].pos=r;
a[tot].t=++t;
a[tot].add=val;
b[top++]=val;
pos[r]=val;
}
}
sort(b,b+top);
top=unique(b,b+top)-b;
for(int i=0;i<top;i++)
mp[b[i]]=i+1;
for(int i=1;i<=tot;i++)
if(a[i].op!=3)
a[i].add=mp[a[i].add];
solve(1,t,1,top+1);
for(int i=1;i<=m;i++)
if(ANS[i]!=-1)
printf("%d\n",b[ANS[i]-1]);
}
}
整体二分AC代码: