红尘路漫漫 愿今生与你共览
一叶轻船
一双桨悠懒
一绵江风微拂素罗衫
渔火点点聚散
欸乃声声浅淡
天近晚
炊烟袅飘沿斑
一叶轻船
一双人倚揽
一曲烟雨行舟太缓慢
执手相看
把酒当歌言欢
红尘路漫漫
愿今生与你共览
\(~~~~\) ——《烟雨行舟》司南
无心学习,前来写题解
「CEOI2022」Abracadabra
题意
\(~~~~\) 有长为 \(n\) 的排列(\(n\) 为偶数),每次“洗牌”定义为将前后均分分为两部分,然后将较小的数加入新序列末尾。
归并,但不完全归并。\(q\) 次询问,每次询问 \(t\) 次洗牌后第 \(x\) 个是多少。
\(~~~~\) \(1\leq x\leq n\leq 2\times 10^5,1\leq q\leq 10^6,0\leq t \leq 10^9\)。
题解
\(~~~~\) 第一感觉打暴力,然后发现序列似乎很快就能稳定下来。事实上我们可以证明最终序列会在 \(n\) 次洗牌内必定稳定,所以我们有了 \(n^2\) 的算法。
\(~~~~\) 然后考虑一个“分块”的思想,由于当某个数被放入新序列末尾时如果紧跟在它后面的数比它小,那肯定这个数也会立刻被放入新序列。所以我们从头开始,将每个数到下一个比它大的数之前所有数化为一块。移动时一块必定就会整体移动。
\(~~~~\) 等等,好像不太对,对于横跨了中轴线的那个块(如果存在),那么它的后半部分就会被划成若干个新的小块。
\(~~~~\) 并且我们发现一个非常优秀的性质:将中间的块后半部分划分后,我们用块首的值来代替整个块的值的话,那一次 “洗牌”就相当于给块做了一次真正的归并排序。那因此:当不存在横跨中轴线的块时序列稳定。
\(~~~~\) 而结合上面拆分块的思想,拆分出来的块显然不会再合并回去,所以最多拆 \(n-1\) 次块,每次有效“洗牌”至少拆一此块,所以 \(n\) 次内必定稳定。
\(~~~~\) 此外还有一个结论:当某个块“洗牌”后完全处于右侧,则它必定不会再移动。结论很好证明,不再赘述。
\(~~~~\) 因此我们可以对这 \(n\) 次直接模拟,每次模拟都直接对块来做。然后对每个块记录块首值、块在原序列的区间。然后用块首值从小到大维护。每次洗牌直接考虑:若末尾的块:
- 完全位于右侧,且没有占满右侧:直接放入确定的部分;
- 刚好占满右侧:直接全部确定;
- 跨过中间:把块拆开重新插入回去。
\(~~~~\) 对于询问显然可以离线下来,考虑“洗牌”后每次怎么求第 \(x\) 个:
- 若位于确定部分,则直接取出;
- 否则可以用平衡树维护块,然后在平衡树上二分前缀即可确定在哪个块。
\(~~~~\) 官方题解的线段树/BIT没有看懂,就不讲了。
代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
struct node{
int val,l,r;
node(){}
node(int Val,int L,int R){val=Val,l=L,r=R;}
};
struct FHQ_Treap{
#define ls lson[p]
#define rs rson[p]
int tot,root;
int lson[5000005],rson[5000005],pri[5000005],siz[5000005],Len[5000005],LLen[5000005];
node val[5000005];
void Update(int p){siz[p]=siz[ls]+siz[rs]+1,Len[p]=Len[ls]+Len[rs]+LLen[p];}
void Split(int p,int v,int &x,int &y)
{
if(!p)
{
x=y=0;
return;
}
if(val[p].val<=v) x=p,Split(rs,v,rs,y);
else y=p,Split(ls,v,x,ls);
Update(p);
}
int Merge(int u,int v)
{
if(!u||!v) return u|v;
if(pri[u]<pri[v])
{
rson[u]=Merge(rson[u],v);
Update(u);
return u;
}
else
{
lson[v]=Merge(u,lson[v]);
Update(v);
return v;
}
}
int NewNode(node x){val[++tot]=x;siz[tot]=1;LLen[tot]=Len[tot]=x.r-x.l+1;pri[tot]=rand();return tot;}
void Insert(node Val)
{
int x,y;
Split(root,Val.val,x,y);
root=Merge(Merge(x,NewNode(Val)),y);
}
void Delete(node Val)
{
int x,y,z;
Split(root,Val.val,x,z);
Split(x,Val.val-1,x,y);
y=Merge(lson[y],rson[y]);
root=Merge(Merge(x,y),z);
}
node GetVal(int Rank)
{
int p=root;
while(p)
{
if(siz[ls]+1==Rank) break;
if(siz[ls]<Rank) Rank-=siz[ls]+1,p=rs;
else p=ls;
}
return val[p];
}
pair<node,int> Query(int x)
{
int p=root;
while(p)
{
if(Len[ls]<x&&Len[ls]+LLen[p]>=x) break;
if(Len[ls]+LLen[p]<x) x-=Len[ls]+LLen[p],p=rs;
else p=ls;
}
x-=Len[ls];
return mp(val[p],x);
}
void DEBUG(int p)
{
if(!p) return;
DEBUG(ls);
printf("%d %d %d\n",val[p].val,val[p].l,val[p].r);
DEBUG(rs);
}
}FHQ;
int n,m,SumLen=0,Block,TURN;
int arr[5000005],Sta[5000005],Top,nxt[5000005];
int Sure[5000005],now;
void Insert(int v,int l,int r)
{
SumLen+=r-l+1;Block++;
FHQ.Insert(node(v,l,r));
}
void Delete(node p)//从末尾滚出去
{
Block--; SumLen-=p.r-p.l+1; FHQ.Delete(p);
for(int i=p.r;i>=p.l;i--) Sure[now--]=arr[i];
}
void Divide(node p)//作为中间块需要裂开
{
Block--; SumLen-=(p.r-p.l+1); FHQ.Delete(p);
int x=p.l+(n/2-SumLen);
Insert(arr[p.l],p.l,x-1);
while(x<=p.r)
{
Insert(arr[x],x,min(nxt[x]-1,p.r));
x=nxt[x];
}
}
bool Suc=false;
int Ans[5000005];
vector <PII> Ask[5000005];
void op()
{
if(!Block) return;
while(1)
{
if(!Block) return;
node x=FHQ.GetVal(Block);
if(SumLen-(x.r-x.l+1)>n/2) Delete(x);
else if(SumLen-(x.r-x.l+1)<n/2){Divide(x);break;}
else {Suc=true;return;}
}
// FHQ.DEBUG(FHQ.root);puts("===");
}
int Query(int x)
{
if(x>now) return Sure[x];
else
{
pair<node,int> P=FHQ.Query(x);
node pos=P.first;
int Rest=P.second;
return arr[pos.l+Rest-1];
}
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
read(n);read(m);now=n;
for(int i=1;i<=n;i++) read(arr[i]);
for(int i=1,t,x;i<=m;i++)
{
read(t);read(x);
t=min(t,n);
if(t==0) Ans[i]=arr[x];
else Ask[t].emplace_back(mp(x,i));
}
for(int i=1;i<=n;i++)
{
while(Top&&arr[Sta[Top]]<arr[i]) nxt[Sta[Top--]]=i;
Sta[++Top]=i;
}
while(Top) nxt[Sta[Top--]]=n+1;
int Ind=1;
while(Ind<=n) Insert(arr[Ind],Ind,nxt[Ind]-1),Ind=nxt[Ind];
// FHQ.DEBUG(FHQ.root);puts("===");
for(int Turn=1;Turn<=n;Turn++)
{
TURN=Turn;
op();
if(Suc) while(Block) Delete(FHQ.GetVal(Block));
for(int i=0;i<Ask[Turn].size();i++)
{
int x=Ask[Turn][i].first,id=Ask[Turn][i].second;
Ans[id]=Query(x);
}
}
for(int i=1;i<=m;i++) printf("%d\n",Ans[i]);
return 0;
}