题目链接:传送门
题目大意:
给出一个数列a,要求资瓷以下两种操作:
1.把
a
[
i
]
a[i]
a[i]的值修改为
t
t
t。
2.询问
[
l
,
r
]
[l,r]
[l,r]区间内的第
k
k
k小值。
时限
2
s
2s
2s,数的个数和操作次数不超过
1
e
5
1e5
1e5。
看到
k
k
k小值就想到用主席树qwq。
然后发现要资瓷修改操作。
不修改的主席树的第
i
i
i棵维护的是
1
~
i
1~i
1~i的前缀。
所以如果要暴力修改,就要把
[
i
,
n
]
[i,n]
[i,n]的主席树都修改一遍,然后复杂度就炸了QAQ。
于是再思考问题的本质:
修改:单点修改。
询问:区间查询时,把
[
1
,
r
]
[1,r]
[1,r]的权值线段树减去
[
1
,
l
−
1
]
[1,l-1]
[1,l−1]的权值线段树
−
>
->
−>区间查询。
单点修改区间查询?
考虑套一个树状数组:
类似于树状数组:
第1棵主席树表示第1个点的权值的集合。
第2棵主席树表示第1、2个点的权值的集合。
第3棵主席树表示第3个点的权值的集合。
第4棵主席树表示第1、2、3、4个点的权值的集合。
这样每次最多修改 l o g n logn logn棵主席树,询问也是 l o g n logn logn棵主席树一起加/减。
几个要注意的点:
1.主席树修改的时候是在自己的版本基础上修改
2.先把所有的询问读进去再离散化,不然新的数在离散化数组中找不到。
总时间复杂度
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。
代码:
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
inline char GetChar() {
char ch=getchar();
while(ch!='Q' && ch!='C') ch=getchar();
return ch;
}
const int Size=100005;
const int LOG=400;
const double eps=1e-4;
namespace I_Love {
int n,m,tot,maxn,a[Size],b[Size<<1],T[Size];
int ls[Size*LOG],rs[Size*LOG],sum[Size*LOG];
int op[Size],x[Size],y[Size],z[Size];
int update(int pre,int l,int r,int x,int val) {
//主席树的修改操作
int rt=++tot;
ls[rt]=ls[pre]; rs[rt]=rs[pre];
sum[rt]=sum[pre]+val;
if(l<r) {
int mid=(l+r)>>1;
if(x<=mid) {
ls[rt]=update(ls[pre],l,mid,x,val);
} else {
rs[rt]=update(rs[pre],mid+1,r,x,val);
}
}
return rt;
}
int lpos,rpos,tmpl[Size],tmpr[Size];
int query(int l,int r,int k) {
//主席树的查询操作
if(l==r) return l;
int mid=(l+r)>>1,ans=0;
//logn棵主席树一起加/减
for(re i=1; i<=lpos; i++) ans-=sum[ls[tmpl[i]]];
for(re i=1; i<=rpos; i++) ans+=sum[ls[tmpr[i]]];
if(k<=ans) {
for(re i=1; i<=lpos; i++) tmpl[i]=ls[tmpl[i]];
for(re i=1; i<=rpos; i++) tmpr[i]=ls[tmpr[i]];
return query(l,mid,k);
}
for(re i=1; i<=lpos; i++) tmpl[i]=rs[tmpl[i]];
for(re i=1; i<=rpos; i++) tmpr[i]=rs[tmpr[i]];
return query(mid+1,r,k-ans);
}
inline int lowbit(int x) {
return x&(-x);
}
void Update(int x,int t) {
//查询a[x]在离散化数组b中的位置
int val=lower_bound(b+1,b+1+maxn,a[x])-b;
for(re i=x; i<=n; i+=lowbit(i)) {
T[i]=update(T[i],1,maxn,val,t);
}
}
int Query(int l,int r,int k) {
lpos=rpos=0;
for(re i=l-1; i; i-=lowbit(i)) {
tmpl[++lpos]=T[i];
}
for(re i=r; i; i-=lowbit(i)) {
tmpr[++rpos]=T[i];
}
return query(1,maxn,k);
}
void Fujibayashi_Ryou() {
maxn=n=read();
m=read();
for(re i=1; i<=n; i++) {
b[i]=a[i]=read();
}
for(re i=1; i<=m; i++) {
char ch=GetChar();
x[i]=read();
y[i]=read();
if(ch=='C') {
op[i]=2;
b[++maxn]=y[i];
} else {
op[i]=1;
z[i]=read();
}
}
sort(b+1,b+1+maxn);
maxn=unique(b+1,b+1+maxn)-(b+1);
for(re i=1; i<=n; i++) {
Update(i,1);
}
for(re i=1; i<=m; i++) {
if(op[i]==2) {
Update(x[i],-1);
a[x[i]]=y[i];
Update(x[i],1);
} else {
printf("%d\n",b[Query(x[i],y[i],z[i])]);
}
}
}
}
int main() {
I_Love::Fujibayashi_Ryou();
return 0;
}
Ps.题目说“写法正常的整体二分和树套树都可以以大约1000ms每个点的时间过”。
然而我开了氧气优化还是跑不进
1
s
1s
1s。。
感受到了题目对大常数选手的恶意QWQ