题面
下面是一段实现冒泡排序算法的C++代码:
for (int i=1;i<=n-1;i++)
for (int j=1;j<=n-i ;j++)
if(a[j]>a[j+1])
swap(a[j],a[j+1]);
其中待排序的a数组是一个1~n的排列,swap函数将交换数组中对应位置的值。
对于给定的数组a以及给定的非负整数k,使用这段代码执行了正好k次swap操作之后数组a中元素的值会是什么样的呢?
n<=1e6 k<=1e12
题解
这题要开long long
这是道结论题,
在尝试了各种方法之后,我们尝试直接构造答案数组。
考虑每个数在答案数组里的位置。
定义j扫一遍为一轮。
分析发现:1:每个数,每轮最多向左移动一个位置。
继续:
2:若一个数左边有比它大的数,它就一定会左移一个位置。
2逆:若它左移一个位置,则它的左边一定有数比它大。再继续:
3:设前面有t个数比它大,那么前t轮,每一轮它都会向左移一个位置,然后它就再不左移。
思路似乎已经清晰。
设经过x轮移动,在过不到一轮就交换k次。
先来考虑经过刚好x轮移动的情况。如果t<=x,那么它的位置在哪里?
这种情况就是,一个数左移到一半就停止不移动了。
显然它的位置是可求的。
如果t>x,那么它的位置又在哪里?
这种数不会再左移,也就说明它的左边没有比它大的数。
推出结论:4:这部分数的位置一定是随着值而递增的。
我们现在已经知道一部分数的具体位置了,把剩下的数按从小到大的顺序填到空位置里就好了。
现在已经知道了x轮移动的答案,还剩下一点交换次数,就直接来一轮冒泡排序就好了。
5:发现:交换次数=左移次数。
根据结论2的逆结论,统计每一轮的左移次数就好了。
- by 我们都是星尘
那么,如果我们知道k次交换后一共完整地进行了x轮的话,就可以把“
for(int i=1; i<n; i++)
” 这重循环给省掉,而直接O(n)计算残缺的第二重循环,既然已经知道x轮移动的答案,而这个答案肯定是单调递增的,就可以通过二分枚举x。
CODE(加freopen)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
#include<algorithm>
#define LL long long
#define MAXN 1000005
#define lowbit(x) (((-x) & (x)))
using namespace std;
inline LL read() {
LL f = 1,x = 0;char s = getchar();
while(s < '0' || s > '9') {if(s == '-') f = -1;s = getchar();}
while(s >= '0' && s <= '9') {x = x * 10 + s - '0';s = getchar();}
return x * f;
}
LL n,m,i,j,s,o,k,cnt;
LL a[MAXN],b[MAXN];
LL c[MAXN];
LL f[MAXN];
priority_queue<int> q;
void addtree(int x,LL y) {
for(int i = x;i <= n;i += lowbit(i)) c[i] += y;return ;
}
inline LL sum(int x) {
LL ans = 0;
for(int i = x;i > 0;i -= lowbit(i)) ans += c[i];
return ans;
}
inline LL check(LL a) {
LL ans = 0;
for(int i = 1;i <= n;i ++) {
ans += min(a,f[i]);
}
return ans;
}
LL solve(LL l,LL r) {
// printf("%d %d\n",l,r);
if(l >= r - 1) {
if(check(r) <= m) return r;
return l;
}
LL mid = (l + r) / 2;
if(check(mid) <= m) return solve(mid,r);
return solve(l,mid);
}
int main() {
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
n = read();m = read();
for(int i = 1;i <= n;i ++) {
a[i] = read();
addtree(a[i],1);
f[i] = sum(n) - sum(a[i]);
}
// for(int i = 1;i <= n;i ++) printf("s:%d ",sum(i));putchar('\n');
LL aa = solve(0,n);
m -= check(aa);
for(int i = 1;i <= n;i ++) {
if(f[i] >= aa) {
b[i - aa] = a[i];
}
else {
q.push(a[i]);
}
}
for(int i = n;i > 0;i --) {
if(!b[i]) {
b[i] = q.top();q.pop();
}
}
// for(int i = 1;i <= n;i ++) {
// printf("%d ",b[i]);
// }putchar('\n');
// printf("%d\n",m);
for(int i = 1;i < n;i ++) {
if(!m) break;
if(b[i] > b[i + 1]) {
swap(b[i],b[i + 1]);
m --;
}
}
if(m > 0) {
printf("Impossible!\n");
}
else {
for(int i = 1;i <= n;i ++) {
printf("%d ",b[i]);
}putchar('\n');
}
return 0;
}