【CEOI2022】Abracadabra
【CEOI2022】Abracadabra
Description
有一个长为\(n\)的排列,会进行若干次操作
每次操作包括:
1.将序列从中间分为数量相同的两堆
2.将两堆的堆底数进行比较,将编号较小的数放下,重复此操作直到一堆消耗完
3.将剩下那一堆全部放下
有\(q\)次询问,每次查询第\(t\)次操作后,第\(i\)个数的值
Input
第一行两个数\(n,q\)
然后一行\(n\)个数读入序列
然后\(q\)行每行两个数读入询问
Output
输出\(q\)行每行一个数表示答案
Sample Input
6 3
1 5 6 2 3 4
1 2
0 4
1 5
Sample Output
2
2
5
Data Constraint
\(1\le n\le 2*10^5,q\le 10^6,t\le 10^9,n\%2=0\)
Solution
从中间切开,实质上就变成了归并排序
考虑归并排序的实质:
选出一个很大的数,然后会选一段比它小的数
也就是按前缀最大值分段,然后将段按前缀最大值排序
然后在不断操作的过程中,段数可能变少,但是至多减少\(n-1\)次
也就是答案\(\le n\)
于是我们用线段树模拟这个过程
观察到一个段内的相对顺序不会变,即在原序列中连续
所以建一颗权值线段树,每个点记录长度和位置
然后再加个二分就能轻松断开或者查询了
Code
#include<bits/stdc++.h>
using namespace std;
#define F(i,a,b) for(int i=a;i<=b;i++)
#define Fd(i,a,b) for(int i=a;i>=b;i--)
#define N 200010
#define M 1000010
#define ls x<<1
#define rs (x<<1)|1
vector<pair<int,int>>ask[N];
int n,m,a[N],rig[N],q[N],top,ans[M],Lbr,Rbr,Mbr;
struct tree{
int sum[N*4],w[N*4];
void change(int x,int l,int r,int pos,int len,int wh){
if(l==r){sum[x]=len;w[x]=wh;return;}
int mid=l+r>>1;
pos<=mid?change(ls,l,mid,pos,len,wh):change(rs,mid+1,r,pos,len,wh);
sum[x]=sum[ls]+sum[rs];
}
int find(int x,int l,int r,int k){
if(l==r){
Lbr=w[x];Rbr=w[x]+sum[x]-1;Mbr=w[x]+k-1;
return a[w[x]+k-1];
}
int mid=l+r>>1;
if(k<=sum[ls])return find(ls,l,mid,k);
return find(rs,mid+1,r,k-sum[ls]);
}
}t;
void constr(int l,int r){
while(l<=r)t.change(1,1,n,a[l],min(r+1,rig[l])-l,l),l=rig[l];
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d%d",&n,&m);
F(i,1,n)scanf("%d",&a[i]);
a[n+1]=2147483647;
q[top=1]=n+1;
Fd(i,n,1){
while(a[i]>a[q[top]]&&top)top--;
rig[i]=q[top];q[++top]=i;
}
constr(1,n);
F(i,1,m){
int t,x;
scanf("%d%d",&t,&x);
t=min(t,n);
ask[t].push_back(make_pair(x,i));
}
for(auto d:ask[0])ans[d.second]=a[d.first];
F(i,1,n){
t.find(1,1,n,n/2+1);
constr(Lbr,Mbr-1);
constr(Mbr,Rbr);
for(auto d:ask[i])ans[d.second]=t.find(1,1,n,d.first);
}
F(i,1,m)printf("%d\n",ans[i]);
return 0;
}