洛谷 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;
} 
posted @ 2025-02-26 15:16  MinimumSpanningTree  阅读(5)  评论(0)    收藏  举报