P2617 分块动态区间第K小
Dynamic Rankings
题目描述
给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作:
Q l r k
表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数C x y
表示将 \(a_x\) 改为 \(y\)
输入格式
第一行两个正整数 \(n,m\),表示序列长度与操作个数。
第二行 \(n\) 个整数,表示 \(a_1,a_2 \dots a_n\)。
接下来 \(m\) 行,每行表示一个操作,都为上述两种中的一个。
输出格式
对于每一次询问,输出一行一个整数表示答案。
样例 #1
样例输入 #1
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
样例输出 #1
3
6
提示
【数据范围】
对于 \(10\%\) 的数据,\(1\le n,m \le 100\);
对于 \(20\%\) 的数据,\(1\le n,m \le 1000\);
对于 \(50\%\) 的数据,\(1\le n,m \le 10^4\);
对于 \(100\%\) 的数据,\(1\le n,m \le 10^5\),\(1 \le l \le r \le n\),\(1 \le k \le r-l+1\),\(1\le x \le n\),\(0 \le a_i,y \le 10^9\)。
分块注意细节!!!
#include<bits/stdc++.h>//分块 动态区间第K小问题
using namespace std;
#define int long long
const int N=1e5+5;
int n,m,len,L[N],R[N],pos[N],a[N],b[N],tot;
void blk_sort(int x)//第x块内排序
{
for(int i=L[x];i<=R[x];i++)b[i]=a[i];
sort(b+L[x],b+R[x]+1);
}
void update(int x,int k)//修改 a[x]=k
{
a[x]=k;
blk_sort(pos[x]);
}
int query(int l,int r,int k)//[l,r]区间中 ≤K の数的个数
{
int cnt=0;
//端点块暴力枚举 中间块利用有序性lower_bound
int x=pos[l],y=pos[r];
if(x==y)
{
for(int i=l;i<=r;i++)
cnt+=(a[i]<=k);
}
else
{
for(int i=l;i<=R[x];i++)
cnt+=(a[i]<=k);
for(int i=L[y];i<=r;i++)
cnt+=(a[i]<=k);
for(int i=x+1;i<=y-1;i++)
cnt+=upper_bound(b+L[i],b+R[i]+1,k)-(b+L[i]-1)-1;
//第一个-1是因为 类比 (b+1,b+n+1,x)-(b+1-1)
//第二个-1是因为upper ---> <= 有个-1
}
return cnt;
}
signed main()
{
ios::sync_with_stdio(false);
cin>>n>>m;
len=sqrt(n);//预处理 自己手写前几个找规律即可
tot=n/len;
if(n%len!=0)tot++;//tot 块数
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;//处理每个i所∈块序数
for(int i=1;i<=tot;i++)//处理L[] R[]
L[i]=(i-1)*len+1,R[i]=i*len;
R[tot]=n;// 最后一块不一定完整
for(int i=1;i<=tot;i++)blk_sort(i);//初始也要有序
while(m--)
{
char op;
cin>>op;
if(op=='Q')
{
int l,r,k;
cin>>l>>r>>k;
//二分答案
int L=1,R=1e9,mid,ans;
while(L<=R)
{
mid=(L+R)>>1;
if(query(l,r,mid)>=k)ans=mid,R=mid-1;//mid处可能为答案 若后面还有满足的就更新 否则说明就是第K小
else L=mid+1;//mid处不满足
}
cout<<ans<<"\n";
}
else
{
int x,y;
cin>>x>>y;
update(x,y);
}
}
return 0;
}