P8996 [CEOI2022] Abracadabra Solution
fhq-treap 好闪,拜谢 fhq-treap。
题解有了权值线段树和树状数组,写发平衡树算是补齐了。
思路1
考场思路,直接模拟,期望
记录
思路2
引理零:最多洗
证明:每次至少交换两个牌的相对位置,之后一定会被卡死或是有序。
洗牌次数不是瓶颈,考虑加速洗牌。
引理一:一个值只会跟着它的前缀最大值走,不会发生相对移动。所以可以把序列看成一个个块,而且这些块的前缀最大值成升序排列。
证明:因为如果
此时,看到下标和前缀最大值都是有序的,而且不是偏序,元素之间有相对顺序,可以考虑用同一棵平衡树维护相对顺序。然后就过了连样例都过不了。
观察样例,发现块与块的相对顺序并不是一成不变的。
引理二:只有跨越
证明:跨越
如果没有块跨越中轴线,不用更改。
发现切割块的过程就是反复去找到最大值,然后把最大值
可见时间复杂度只和块的个数
提醒:注意平衡树实现时的常数。
其余的看注释理解吧。
AC Code
#include<bits/stdc++.h>
#define in inline
#define re register
#define ll long long
#define ull unsigned long long
#define mp make_pair
#define pb emplace_back
#define sort stable_sort
#define REP(i,l,r) for(register int i=l;i<=r;++i)
#define PER(i,r,l) for(register int i=r;i>=l;--i)
using namespace std;
random_device R;
mt19937 G(R());
inline int rd(int l,int r){return uniform_int_distribution<int>(l,r)(G);}
const int inf=1e9+7;
const ll INF=1e18+7;
char buf[1<<19],*p1=buf,*p2=buf;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<19,stdin),p1==p2)?EOF:*p1++)
static inline int read() {
register int x=0,w=1;
register char ch=gc();
while((ch<48||ch>57)&&ch!='-') ch=gc();
if(ch=='-')w=-1,ch=gc();
while(ch>=48&&ch<=57) x=(x<<3)+(x<<1)+(ch^48),ch=gc();
return x*w;
}
static inline char readc(){
register char ch=gc();
while(isspace(ch))ch=gc();
return ch;
}
namespace Code{
const int N=2e5+7;
const int Q=1e6+7;
int n,q;
int arr[N],pri[N],pmax[N];
int Ans[Q];
struct lyt{int t,ith,id;}In[Q];
bool cmp(lyt a,lyt b){return a.t<b.t;}
struct box{
int son[2];
int pri;//随机堆权值
int sz,val,pmax;//子树大小,当前点权值,前缀最大值
int ma,maid;//最大值,最大值原序列编号
int id,tg;//当前值原序列编号,覆盖前缀最大值的标记
box(){son[0]=son[1]=pri=sz=val=pmax=id=ma=maid=tg=0;}
}tr[N];
int rt=0,trc=0;
#define ls tr[x].son[0]
#define rs tr[x].son[1]
inline void pu(int x){//更新最大值和最大值编号
tr[x].sz=tr[ls].sz+tr[rs].sz+1;
tr[x].ma=std::max(tr[x].val,std::max(tr[ls].ma,tr[rs].ma));
if(tr[x].ma==tr[x].val) tr[x].maid=tr[x].id;
else if(tr[x].ma==tr[ls].ma)tr[x].maid=tr[ls].maid;
else if(tr[x].ma==tr[rs].ma)tr[x].maid=tr[rs].maid;
}
inline void pd(int x){
if(tr[x].tg){//更新前缀最大值
tr[ls].pmax=tr[x].tg,tr[rs].pmax=tr[x].tg;
tr[ls].tg=tr[rs].tg=tr[x].tg;
tr[x].tg=0;
}
}
void splitsz(int x,int &l,int &r,int key){//按相对排名分裂
if(!x){l=r=0;return;}
if(tr[ls].sz+1<=key){l=x,pd(x),splitsz(rs,tr[l].son[1],r,key-tr[ls].sz-1),pu(x);}
else {r=x,pd(x),splitsz(ls,l,tr[r].son[0],key),pu(x);}
}
void splitpm(int x,int &l,int &r,int key){//按前缀最大值分裂
if(!x){l=r=0;return;}
if(tr[x].pmax<=key){l=x,pd(x),splitpm(rs,tr[l].son[1],r,key),pu(x);}
else {r=x,pd(x),splitpm(ls,l,tr[r].son[0],key),pu(x);}
}
void merge(int &x,int l,int r){
if(!l||!r){x=l|r;return;}
if(tr[l].pri<tr[r].pri){x=l,pd(x),merge(rs,tr[l].son[1],r),pu(x);}
else {x=r,pd(x),merge(ls,l,tr[r].son[0]),pu(x);}
}
int Build(int l,int r){//建树
if(l>r)return 0;
int node=++trc,mid=(l+r)>>1;
tr[node]=box();
tr[node].id=mid;
tr[node].pri=pri[node];
tr[node].val=arr[mid],tr[node].pmax=pmax[mid],tr[node].sz=1;
tr[node].son[0]=Build(l,mid-1);
tr[node].son[1]=Build(mid+1,r);
pu(node);return node;
}
int now=0;
int ask_kth(int x,int key){//递归找 k 大值
pd(x);
// cout<<x<<' '<<key<<"ASK\n";
if(tr[ls].sz>=key) return ask_kth(ls,key);
else if(tr[ls].sz+1>=key)return x;
else return ask_kth(rs,key-tr[ls].sz-1);
}
void Wash(){//一次洗牌
int lr=ask_kth(rt,(n>>1)),rl=ask_kth(rt,(n>>1)+1),rr=ask_kth(rt,n);
if(tr[lr].pmax!=tr[rl].pmax)return;//没有块被切开
int ma,maid;
int ltr=0,rtr=0,fhq=0,node=0;
splitsz(rt,ltr,rtr,(n>>1));//沿中轴线切割块
splitpm(rtr,rtr,fhq,tr[rl].pmax);//切割出要改变的块rtr
int L=tr[rl].id,R=tr[rl].id+tr[rtr].sz-1;
do{
maid=tr[rtr].maid,ma=tr[rtr].ma;//块中最大值编号,值
splitsz(rtr,rtr,node,maid-L);//块中新的最大值作为前缀最大值的块
tr[node].tg=ma,tr[node].pmax=ma;//更新前缀最大值
int ltrl=0,ltrr=0;
splitpm(ltr,ltrl,ltrr,tr[node].pmax);
merge(ltrl,ltrl,node),merge(ltr,ltrl,ltrr);//把新块按照前缀最大值中序遍历插入到前半部分
R=maid-1;
}while(maid!=L);
merge(rtr,rtr,fhq);
merge(rt,ltr,rtr);//合并回去
}
int solve(int ti,int kth){
ti=std::min(ti,n);//最多洗 n 次牌
// cout<<ti<<" "<<kth<<"Solve:\n";
while(now!=ti)Wash(),++now; //模拟洗牌
// cout<<"SOVLE:"<<ask_kth(rt,kth)<<' '<<tr[ask_kth(rt,kth)].val<<'\n';
return tr[ask_kth(rt,kth)].val;//此时平衡树的中序遍历就是牌堆
}
signed main(){
n=read(),q=read();
REP(i,1,n)arr[i]=read(),
pmax[i]=std::max(pmax[i-1],arr[i]),
pri[i]=rd(pri[i-1],10000*i);
rt=Build(1,n);
// Treer("YU",rt);
REP(i,1,q)In[i].t=read(),
In[i].ith=read(),
In[i].id=i;
stable_sort(In+1,In+q+1,cmp);
REP(i,1,q){Ans[In[i].id]=solve(In[i].t,In[i].ith);}//离线处理询问
REP(i,1,q)cout<<Ans[i]<<'\n';
return 0;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
Code::main();
return 0;
}