冒泡排序二合一 题解

冒泡排序二合一 题解


知识点

线段树,树状数组,主席树。

题意简述

给定一个排列 \(a\),有下列两种询问:

  1. 给定 \(l,r,k,x\),求将 \(a\) 的区间 \([l,r]\) 冒泡排序 \(k\) 轮之后,\(x\) 这个数在数组中的位置。
  2. 给定 \(l,r,k,x\),求将 \(a\) 的区间 \([l,r]\) 冒泡排序 \(k\) 轮之后,\(x\) 这个位置的数是多少。

每个询问独立。

定义对 \(a\) 的区间 \([l,r]\) 进行一轮冒泡排序为依次对于 \(i=l,l+1,\dots,r-1\),若 \(a_i>a_{i+1}\),则交换 \(a_i,a_{i+1}\)

分析

首先这明显是一道缝合题,要分成两部分求解。其次注意如查询的数不在 \([l,r]\) 内需要特判。

TYPE 1

这个部分比较简单。

首先很直观地可以感受到,在一轮排序中,一个数如果前面有大于它的数,结束后它会往前移动一个位置;否则它会移动到后面第一个大于它的值前面。

那么 \(k\) 轮其实是连续的过程,一个数肯定是先连续几次往前移动(可以是 \(0\) 次),然后再一直跑到第一个大于它的值前面,最后不动。

所以我们可以按询问中的 \(x\) 降序做扫描线,扫出在 \(x\) 之前有几个大于它,设为 \(s\)

  • 如果 \(k\le x\),其实就是往前移动 \(k\) 位。

  • 如果 \(k>x\),除了先往前移动 \(s\) 位,还需要进一步考虑后面的影响:

    发现当 \(x\) 第一次往后移动,它跳到了 \([l,r]\) 中第 \(s+1\) 个大于它的值,依次类推,第 \(k-s\) 次就是第 \(k\) 个大于它的值。除非没有这么多大于它的值,那么直接找到停止位置即可。

    所以我们可以考虑找到 \([l,r]\) 中第 \(k\) 个大于 \(x\) 的值的位置,减去 \(k\) 即可。

可以用树状数组实现,也可以用线段树。

TYPE 2

这个部分稍难。

首先你要发现这个可以二分,那么就有 \(O(n\log^2{n})\) 的整体二分做法:

检查答案是否 \(\ge lim\),考虑把 \(\ge lim\) 的点都视作 \(1\),其余为 \(0\),我们只需要考虑排完序之后 \(x\) 这个位置的值是否为 \(1\)。那么很简单,排序 \(k\) 轮就是把前 \(k\)\(1\) 放到序列最后,其余 \(1\) 往前移动 \(k\) 位(不足 \(k\)\(1\) 就全部放到最后)。

现在考虑如何优化,思考二分过程,发现可以分为两部分:

  1. \(x \ge r-k+1\),那么一定是在最后的 \(k\) 个位置里,区间第 \(k\) 大即可解决。

  2. 否则在二分时,\(x\) 处的值取决于 \(x+k\) 处的值,原因很简单:

    • \(x+k\) 前有 \(\ge k\)\(1\) 时,它会向前移动 \(k\) 位。
    • \(x+k\)\(1\) 的个数 \(<k\) 时,此时 \(x+k\) 之前的 \(1\) 全部会跑到序列最后,但是 \(x\) 处的值会取到原本在 \(<x+k\) 处的值,那么必定是 \(0\)

    那么只需考虑 \(x+k\) 前有 \(\ge k\)\(1\) 的情况,求出 \(<x+k\) 的第 \(k\) 大值 \(s\),在 \(lim \le s\) 时都是满足这种情况的,只要再考虑 \(a_{x+k}\) 即可,答案即为 \(\min(a_{x+k},s)\)

上述操作可以主席树实现。

代码

constexpr int N(6e5+10);

int n,Q,TYPE;
int a[N],p[N];

struct BIT {
	int c[N];

	void Plus(int x) { if(x>0)for(; x<=n; x+=x&-x)++c[x]; }

	int Sum(int x) {
		int ans(0);
		if(x<=n)for(; x>0; x&=x-1)ans+=c[x];
		return ans;
	}

	int Sum(int l,int r) { return l<=r?Sum(r)-Sum(l-1):0; }

	int Double(int k) {
		int ans(0),sum(0);
		DOR(i,19,0)if((ans|1<<i)<=n&&sum+c[ans|1<<i]<k)sum+=c[ans|=1<<i];
		return ans+(sum<k);
	}
} bit;

namespace T1 {
	int ans[N];
	struct Query { int l,r,k,idx; };
	vector<Query> Que[N];

	int Main() {
		/*DE("Offline");*/
		FOR(i,1,Q) {
			int l,r,k,x;
			I(l,r,k,x);
			if(p[x]<l||p[x]>r||!k) {
				ans[i]=p[x];
				continue;
			}
			Que[x].push_back({l,r,k,i});
		}
		/*DE("Solve");*/
		DOR(x,n,1) {
			bit.Plus(p[x]);
			for(const Query &que:Que[x]) {
				const int &l(que.l),&r(que.r),&k(que.k);
				int lef(bit.Sum(l,p[x]-1));
				if(k<=lef) {
					ans[que.idx]=p[x]-k;
					continue;
				}
				int ord(bit.Sum(l-1)+k+1);
				int pos(min(r,bit.Double(ord)));
				int rig((pos-p[x])-bit.Sum(p[x]+1,pos));
				ans[que.idx]=p[x]-lef+rig;
			}
		}
		/*DE("Output");*/
		FOR(i,1,Q)O(ans[i],'\n');
		return 0;
	}
}

struct SEG {
	int tot;
	int rt[N];
	struct node {
		int ls,rs,sum;
		node():ls(0),rs(0),sum(0) {}
	} tr[N*21];
	SEG():tot(0) {}

	int &operator [](int i) { return rt[i]; }
#define ls(p) (tr[p].ls)
#define rs(p) (tr[p].rs)
#define mid ((l+r)>>1)
	void Insert(int x,int &p,int q,int l=1,int r=n) {
		tr[p=++tot]=tr[q],++tr[p].sum;
		if(l==r)return;
		return x<=mid?Insert(x,ls(p),ls(q),l,mid):Insert(x,rs(p),rs(q),mid+1,r);
	}

	int Kth(int k,int p,int q,int l=1,int r=n) {
		if(l==r)return l;
		int sum(tr[rs(q)].sum-tr[rs(p)].sum);
		if(k<=sum)return Kth(k,rs(p),rs(q),mid+1,r);
		return Kth(k-sum,ls(p),ls(q),l,mid);
	}
#undef ls
#undef rs
#undef mid
} seg;

namespace T2 {
	int Main() {
		/*DE("Init");*/
		FOR(i,1,n)seg.Insert(a[i],seg[i],seg[i-1]);
		/*DE("Query");*/
		while(Q--) {
			int l,r,k,x;
			I(l,r,k,x);
			if(x<l||x>r||!k) {
				O(a[x],'\n');
				continue;
			}
			if(x>=r-k+1) {
				O(seg.Kth(r-x+1,seg[l-1],seg[r]),'\n');
				continue;
			}
			O(min(a[x+k],seg.Kth(k,seg[l-1],seg[x+k-1])),'\n');
		}
		return 0;
	}
}

signed main() {
	/*DE("Input");*/
	I(n,Q,TYPE);
	FOR(i,1,n)I(a[i]),p[a[i]]=i;
	return TYPE==1?T1::Main():T2::Main();
}
posted @ 2025-11-24 21:57  Add_Catalyst  阅读(8)  评论(0)    收藏  举报