题目
题目描述
下面是一段实现冒泡排序算法的
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}
n≤106,k≤1012 。
思路
给大家看一个 手画的、丑的不堪入目的 冒泡排序过程图——
旁边的红色标记是指针的意思(也就是所谓的
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=1x−1[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;
}