洛谷 AT_joisc2016_h 回転寿司 题解
由于这题是修改与查询操作,所以可以考虑用分块维护。
这题的序列虽然是一个环,但是我们无须理会,可以当成普通序列来做,只需要在 \(l>r\) 的时候拆成两部分来跑就行。
对于每次分块操作,分为左右两端的散块和中间的整块,下面讲解两种情况分别如何处理。
整块处理
因为 \(A_i\) 一遇到比它大的数就和它交换,所以 \(A_i\) 经过交换后的数一定是原来 \(A_i\) 的值与整块中的数的最大值。但是因为有多个查询,之前的 \(A_i\) 来这个块跑过,这个块的最大值就可能变了,所以可以使用单调队列,每个块维护一个大根堆。\(A_i\) 要和当前这个块做交换操作时把 \(A_i\) 丢进去再取堆顶就可以了。
散块处理(本题难点)
上面的整块处理仅仅维护了块内数的堆,所以通过堆,经过修改的块内数只能知道有什么数,无法知道到底是什么顺序,这样的话就影响到散块了。有没有什么办法能复原散块的顺序呢?
块内的数在正常情况下是不会被改变的,如果被改变了,那一定是前面整块时的 \(A_i\) 导致的。前面这么多次修改,怎么知道最终被换成了哪个数呢?可以想到,只要每次遇到比自己小的 \(A_i\),就要和它交换。所以如果被修改过的话,最终一定被修改成了最小的那个数。这个地方的处理方式挺巧妙的,方法是优先队列每个块维护一个小根堆,存储这个块所有曾经整块处理时的 \(A_i\),它们遍历交换时都有可能修改块内的数,所以我们整块处理的时候就可以先把 \(A_i\) 入队了。散块处理的时候,遍历块内所有数,我们不知道当前这个数会不会被修改,所以就直接把数丢进小根堆里,再取最小的出来就是修改后的数。不会被修改的话,说明没有小于它的 \(A_i\),取出来的自然还是原来的数。如果会被修改,取出来的是最小的 \(A_i\) 也就是最终被修改成的数,同时该数原来的值也被留在了队列内,就完美地完成了该数与修改它的 \(A_i\) 的交换操作。
实现的细节可以看看代码和注释。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
const int N=4e5+100,M=1100;
int n,q,block,nk,st[M],ed[M],p[N],a[N],l,r,num;
priority_queue<int> q1[M];//大根堆,维护整块最大值
priority_queue<int,vector<int>,greater<int> > q2[M];//小根堆,维护会影响散块的x
void build()//预处理
{
block=sqrt(n);
nk=n/block;
if(n%block!=0) nk++;
for(int i=1;i<=nk;i++) st[i]=(i-1)*block+1,ed[i]=st[i]+block-1;
ed[nk]=n;
for(int i=1;i<=n;i++)
{
p[i]=(i-1)/block+1;
q1[p[i]].push(a[i]);
}
}
int update(int k,int ql,int qr,int x)//处理散块(排好顺序+交换更新x值)
{
for(int i=st[k];i<=ed[k];i++)//排好这个块的顺序(复原这个块)
{
//将原来的数丢进去,如果遇到了更小的数说明a[i]与前面的x交换了
q2[k].push(a[i]);
a[i]=q2[k].top(),q2[k].pop();
}
for(int i=ql;i<=qr;i++)//由于是散块,直接遍历交换更新x的值
{
if(a[i]>x) swap(x,a[i]);
}
while(!q2[k].empty()) q2[k].pop();//已将块k更新成最新状态,没有x可以影响它了,清空
//由于受到x交换的影响,接下来要更新存储块k的堆
while(!q1[k].empty()) q1[k].pop();
for(int i=st[k];i<=ed[k];i++) q1[k].push(a[i]);
return x;
}
int query(int ql,int qr,int x)//分块处理查询
{
if(p[ql]==p[qr]) return update(p[ql],ql,qr,x);//处于同一块,直接处理
x=update(p[ql],ql,ed[p[ql]],x);//处理左边散块
for(int i=p[ql]+1;i<=p[qr]-1;i++)//处理中间整块
{
q1[i].push(x);
q2[i].push(x);//此整块处理会影响后面i块处理散块时的顺序,存进小根堆
x=q1[i].top(),q1[i].pop();//更新x的值
}
return update(p[qr],st[p[qr]],qr,x);//处理右边散块
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build();
while(q--)
{
scanf("%d%d%d",&l,&r,&num);
if(l<=r) printf("%d\n",query(l,r,num));
else printf("%d\n",query(1,r,query(l,n,num)));//是一个环,所以在n~1之间连和的情况时,可直接拆成两半做
}
return 0;
}

浙公网安备 33010602011771号