红尘路漫漫 愿今生与你共览

一叶轻船
一双桨悠懒
一绵江风微拂素罗衫
渔火点点聚散
欸乃声声浅淡
天近晚
炊烟袅飘沿斑
一叶轻船
一双人倚揽
一曲烟雨行舟太缓慢
执手相看
把酒当歌言欢
红尘路漫漫
愿今生与你共览
\(~~~~\) ——《烟雨行舟》司南

无心学习,前来写题解

「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;
}
posted @ 2022-08-11 22:08  Azazеl  阅读(110)  评论(0编辑  收藏  举报