[usOJ6310]冒泡排序

题目

传送门 to usOJ

题目描述
下面是一段实现冒泡排序算法的 C++ \text{C++} C++ 代码:

for(int i=1; i<n; i++)
	for(int j=1; j<=n-i; j++)
		if(a[j] > a[j+1])
			swap(a[j],a[j+1]);

其中待排序的 a a a 数组是一个 1 , 2 , 3 , … , n 1,2,3,\dots,n 1,2,3,,n 的排列, s w a p \tt{swap} swap 函数将交换数组中对应位置的值。

对于给定的数组 a a a 以及给定的非负整数 k k k ,使用这段代码执行了正好 k k k s w a p \tt{swap} swap 操作之后数组 a a a 中元素的值会是什么样的呢?

输入格式
输入文件共 2 2 2 行。第 1 1 1 行包含空格隔开的一个正整数 n n n 和一个非负整数 k k k ;第 2 2 2行包含 n n n 个空格隔开的互不相同的正整数,表示初始时 a a a 数组中的排列。

输出格式
输出文件共 1 1 1 行。若在执行完整个代码之后执行 s w a p \tt{swap} swap 的次数仍不够 ,那么输出一个字符串 “ I m p o s s i b l e ! ” \tt{“Impossible!”} Impossible!(不含引号),否则按顺序输出执行 s w a p \tt{swap} swap 操作 k k k 次之后数组 a a a 的每一个元素,用空格隔开。

数据范围与约定
在这里插入图片描述
对于 100 % 100\% 100%的数据, n ≤ 1 0 6 , k ≤ 1 0 12 n\le10^6,k\le10^{12} n106,k1012

思路

给大家看一个 手画的、丑的不堪入目的 冒泡排序过程图——

在这里插入图片描述
旁边的红色标记是指针的意思(也就是所谓的 j j j)。

你看,我们“冒泡”的过程,更像踢足球的过程。一开始球在 3 3 3 手里,然后就被 6 6 6 抢断了; 6 6 6 一路过人,过掉了比自己弱的 2 , 1 , 5 , 4 2,1,5,4 2,1,5,4 ,然后又被 7 7 7 抢断了; 7 7 7 射门了!结果发现是自家的门。

球回到第一个人手里,再来一次。比如图中的情况, 3 3 3 跑到了 5 5 5 前面, 5 5 5 传给了 6 6 6 6 6 6 肝不动 7 7 7 ……

所以,明确这一点:“运球的球员”只会越来越大

谁会过掉你呢?是比你大的人。而且在你前面。所以我们往逆序对这方面思考。

结论:将 j j j 的循环(内层循环)跑一次叫做“一轮”,每一轮中,如果 x x x 的左边有比自己大的数字,那个比自己大的数字就会越过 x x x ,其余数字的相对位置不变;否则 x x x 就会向右移动。

首先,如果 x x x 被越过了,每一轮最多只有一个数字会越过 x x x 。因为只有一个数字在“运球”。

如果 x x x 左边有比它大的数字,球要么去了它手上,要么是一个比它还强的球员正在运球。无论怎样,由于运球的球员越来越大,最终这个人一定会过掉 x x x

反之,如果左边没有比 x x x 强的球员, x x x 一定会把球断下来。

断言:每一轮最多只有一个数字越过 x x x ,其充要条件是 x x x 左边有比它大的数字;其余数字与 x x x 的相对位置不变

假设跑了 ω \omega ω ,每一个数字会被多少个数字跨过?设 b x = ∑ i = 1 x − 1 [ a i > a x ] b_x=\sum_{i=1}^{x-1}[a_i>a_x] bx=i=1x1[ai>ax] ,显然有两个限制条件:

  • 答案不超过 b x b_x bx 。因为左边只有 b x b_x bx 个数字可以越过它。
  • 答案不超过 ω \omega ω 。因为每一轮只有一个数字可以越过它。

所以答案就是 min ⁡ ( b x , ω ) \min(b_x,\omega) min(bx,ω) 。而 s w a p \tt swap swap 的本质就是一次跨越,我们将其记录在被跨过的点身上,不难发现,跨越和 s w a p \tt swap swap 一一对应。于是总 s w a p \tt swap swap 次数就是 ∑ i = 1 n min ⁡ ( b i , ω ) \sum_{i=1}^n\min(b_i,\omega) i=1nmin(bi,ω)

所以我们可以二分 ω \omega ω 然后判断,是否交换了至少 k k k 次。于是我们就求出了跑了多少轮。

求出多少轮之后,我们只需要知道 a a a 是什么样子即可——最后一轮可以暴力模拟。

怎么求 a a a 数组长什么样子呢?

对于 b x ≥ ω b_x\ge\omega bxω x x x ,因为它左边有 ω \omega ω 个数字跨过了它,于是它就移步到了原位置向左移动 ω \omega ω 步的地方。

把这些 x x x 填好之后,就留下了一些坑,我们用剩下的数字来填。

剩下的数字是 b x < ω b_x<\omega bx<ω x x x ,于是一定存在某个时刻, x x x 的左边已经没有比它小的数了。逆序对是不增的,所以直到最后, x x x 的左边也没有比它小的数。既然如此,剩下的数字中最小的一个,就会是最左边的一个坑(否则更左边的坑中就有了比它大的数字);第二小,就是左数第二个坑;其余同理。

于是我们把剩下的数字存储下来,排序,然后依次塞到坑里去。

时间复杂度:计算 b b b 时可以使用树状数组, O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) ;二分与判断,一共 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) ;排序与填空,一共 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}

const int MaxN = 1000005;
int a[MaxN], n; long long k;

void input(){
	n = readint(); scanf("%lld",&k);
	for(int i=1; i<=n; ++i)
		a[i] = readint();
}

int b[MaxN]; /* a[i]前面有多少个比a[i]小的数字 */
int BIT[MaxN]; /* 树状数组 */
void getB(){
	for(int i=1; i<=n; ++i){
		/* 经典问题:树状数组求逆序对 */
		b[i] = 0;
		for(int j=n+1-a[i]; j; j-=(j&-j))
			b[i] += BIT[j];
		for(int j=n+1-a[i]; j<=n; j+=(j&-j))
			++ BIT[j];
	}
}
long long check(int x){
	long long res = 0;
	for(int i=1; i<=n; ++i)
		res += min(x,b[i]);
	return res;
} /* 多年准备一场空,不开 long long 见祖宗 */
int tmp[MaxN], bucket[MaxN];
void solve(){
	getB();
	int L = 0, R = n, mid;
	if(check(n) < k){ /* 即使跑n轮都搞不定k */
		puts("Impossible!");
		return;
	}
	while(L != R){ /* 二分呗,还能是啥 */
		mid = (L+R+1)>>1;
		if(check(mid) <= k)
			L = mid;
		else R = mid-1;
	}
	k -= check(L); /* 把前面那几轮的swap次数减掉 */
	int jb = 0; /* 剩下的是嘉(jia)宾(bin) */
	for(int i=1; i<=n; ++i) /* 直接定位 or 收集 */
		if(b[i] >= L) tmp[i-L] = a[i];
		else bucket[++ jb] = a[i];
	sort(bucket+1,bucket+jb+1); /* 排序 */
	for(int i=1,ppl=1; i<=n; ++i) /* 填坑 */
		if(not tmp[i]) tmp[i] = bucket[ppl ++];
	swap(tmp,a); /* 将tmp拷贝给a,据说C++11就是O(1) */
	for(int i=1; i<n and k; ++i)
		if(a[i] > a[i+1]) /* 暴力模拟 */
			swap(a[i],a[i+1]), -- k;
	for(int i=1; i<=n; ++i)
		writeint(a[i]), putchar(' ');
}

int main(){
	freopen("sort.in","r",stdin);
	freopen("sort.out","w",stdout);
	input(), solve();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值