「题解」「2014 NOI模拟赛 Day7」冒泡排序
「题解」「2014 NOI模拟赛 Day7」冒泡排序
题目勾起了我对我蒟蒻时代的回忆,虽然我现在也蒟蒻
题目
可能链接会挂,在网上搜题目就有。
毕竟 BZOJBZOJBZOJ 有点老了…
考场思考
本来以为十分友善的一道题…哎…
考试的时候这样想的:
定义 ptr[i]ptr[i]ptr[i] 表示从第 iii 位开始,往右边遇到的第一个大于 a[i]a[i]a[i] 的数的下边。
考虑每次一轮就是把 a[i]a[i]a[i] 放到 ptr[i]−1ptr[i]-1ptr[i]−1 的位置,这样一共需要 ptr[i]−1−iptr[i]-1-iptr[i]−1−i 次 swapswapswap 操作的机会。
这样做似乎是可行的,但是维护 ptrptrptr 数组太难了,太难了…
然后不得不拿个 30pts30pts30pts 的暴力分,无奈 AFOAFOAFO …
正解
这居然是道二分,道二分!!!
首先,我们考虑冒泡排序的本质是什么:
先看一看普通的冒泡排序代码: 没错就是题目中的
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]);
然后,我们展开分析
- 对于外层循环一圈结束之后,第 iii 大的数会跑到最后边去,同时也交换了路上的某些点,为了方便表示,我们把外层循环一边成为一个回合。
- 内层循环中 jjj 指向的数字(即 a[j]a[j]a[j] )一定是 jjj 遍历到的最大的数,即若 a[j]<a[j+1]a[j]<a[j+1]a[j]<a[j+1] ,那么 jjj 会换一个指向对象,从 a[j]a[j]a[j] 指向 a[j+1]a[j+1]a[j+1] ,反之,如果 a[j]>a[j+1]a[j]>a[j+1]a[j]>a[j+1] ,那么 jjj 会保持指向 a[j]a[j]a[j] 。
- 一个数能往左走,当且仅当它的左边有某一个数比它大。
- 反之,一个数如果左边有比它大的数,那么这个回合它一定会往左走。
我们再考虑一个东西:每一个回合,除这个序列已经有序的情况外,是一定存在交换操作的。
那么,当我们的回合数 xxx 增加时,操作数 optoptopt 也是会增加的。
有了这个,自然而然考虑二分。
怎么二分?考虑二分回合数 midmidmid 。
有了 midmidmid ,只需要计算一下当我们经过 midmidmid 个回合之后,使用了多少次 swapswapswap 。
怎么算呢?考虑我们刚刚分析的规律:
- 一个数能往左走,当且仅当它的左边有某一个数比它大。
- 反之,一个数如果左边有比它大的数,那么这个回合它一定会往左走。
我们记位置为 iii 的数 a[i]a[i]a[i] 的左边有 t[i]t[i]t[i] 个比它大的。
那么,我们考虑,如果满足 t[i]>0t[i]>0t[i]>0 ,那么每进行完一个回合, t[i]t[i]t[i] 应该是减小 111 ,同时 a[i]a[i]a[i] 应该往左移动一个单位,而往左移动一格,也就是提供了一次 swapswapswap 的使用。
也就是说,如果 mid≤t[i]mid\le t[i]mid≤t[i] ,那么进行完 midmidmid 个回合之后, a[i]a[i]a[i] 的左边仍然有比它大的,但同时 a[i]a[i]a[i] 也往左边移动了 midmidmid 位,而且它提供了 midmidmid 次 swapswapswap 的使用次数。
但是,如果 t[i]<midt[i]<midt[i]<mid 呢?
这就说明,对于 midmidmid 个回合之后的 a[i]a[i]a[i] ,从它的位置往左数,已经没有数比它大了。
假设这时 a[i]a[i]a[i] 的位置是 ppp ,那么满足 ∀j∈[1,p),a[i]>a[j]\forall j\in [1,p),a[i]>a[j]∀j∈[1,p),a[i]>a[j] 。
而且,这个时候, a[i]a[i]a[i] 已经没有再往左走的机会了。
也就是说,如果对于 a[i]a[i]a[i] 结束时的目标点 p′p'p′ ,如果 p′<pp'<pp′<p ,那么 a[i]a[i]a[i] 就永远也没机会到它原本的位置了。
那么我们可以确定, p≤p′≤Np\le p'\le Np≤p′≤N 。
然后,不幸的消息出现了,如果我们再这样推下去,推到后面,就推不动了,然后 ++ZiBi++ZiBi++ZiBi …
具体为什么,可以去试一下,嘿嘿嘿…
正确地分析一下此题 (来自于 ljljlj 大佬)
首先继承一下之前的烂尾分析:
- 当 mid≤t[i]mid\le t[i]mid≤t[i] 时,这个数使用了 midmidmid 次 swapswapswap
- 当 t[i]<midt[i]<midt[i]<mid 时,这个数使用了 t[i]t[i]t[i] 次 swapswapswap
那么,对于一个回合数 midmidmid ,我们可以 O(N)O(N)O(N) 地扫描一遍,得到它在 midmidmid 回合后,使用的 swapswapswap 的次数 tottottot 。
那么,当 tot>Ktot>Ktot>K 时,显然这个 midmidmid 不合法。
记 calc(x)calc(x)calc(x) 为 xxx 回合后,使用的 swapswapswap 次数。
否则,我们就去找 ∀mid,max{calc(mid)∣calc(mid)≤K}\forall mid,max\{calc(mid)|calc(mid)\le K\}∀mid,max{calc(mid)∣calc(mid)≤K} 。
用人话说就是,找到所有的 midmidmid 中,使得 calc(mid)calc(mid)calc(mid) 最接近 KKK 但又比 KKK 小。
显然这是可以用二分做到的。
找到这个 midmidmid 有什么用呢?我们可以保证在 midmidmid 个回合后,如果再来一个回合,会使得一共使用的 swapswapswap 次数大于 KKK ,而我们还剩下的 K−calc(mid)K-calc(mid)K−calc(mid) 次交换,可以再用一个 O(N)O(N)O(N) 来暴力解决。
接下来考虑我们已经得到了 midmidmid ,那么怎么计算进行完 midmidmid 个回合之后,每个数的位置。
对于一个数 a[i]a[i]a[i] ,如果 mid≤t[i]mid\le t[i]mid≤t[i] ,那么这个数往左移动 midmidmid 个位置。
但是,如果 t[i]<midt[i]<midt[i]<mid ,也就是说它不会一直往左移动,还有可能往右移动,这个时候怎么解决?
我们用一个例子来说明:
假定我们有一个序列:
N=6,arr={3,5,1,6,2,4}N=6,arr=\{3,5,1,6,2,4\}N=6,arr={3,5,1,6,2,4}
假设 mid=2mid=2mid=2 ,最终的数组为 goal[]goal[]goal[] , 那么操作过程如下:
循环第一遍: i=6i=6i=6 ( iii 从大到小遍历, iii 表示数值,而非位置)
因为 mid=2mid=2mid=2 ,所以前 222 大的数都到了目标位置。
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 6 |
循环第二遍: i=5i=5i=5
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 5 | 6 |
循环第三遍: i=4i=4i=4 ,而 t[6]==2t[6]==2t[6]==2 (数字 444 位置在 666 )
因为 t[6]≤midt[6]\le midt[6]≤mid ,所以数字 444 从它原来的位置 666 往左移动 222 ,到位置 444 ,那么
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 4 | 5 | 6 |
循环第四遍: i=3i=3i=3 ,而 t[1]=0t[1]=0t[1]=0 。
此刻 mid>t[1]mid>t[1]mid>t[1] ,让这个数字待定 。
此刻的数组:
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 4 | 5 | 6 | |||
待定数组 | 3 | \ | \ | \ | \ | \ |
循环第五遍: i=2i=2i=2 ,而 t[5]=3t[5]=3t[5]=3 。
此刻 mid≤t[5]mid\le t[5]mid≤t[5] ,这个数字往左移动 midmidmid 位,那么:
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 2 | 4 | 5 | 6 | ||
待定数组 | 3 | \ | \ | \ | \ | \ |
循环第六遍: i=1i=1i=1 ,而 t[3]=2t[3]=2t[3]=2 。
此刻 mid≤t[3]mid\le t[3]mid≤t[3] ,所以这个数字往左移动 midmidmid 位,那么
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 1 | 2 | 4 | 5 | 6 | |
待定数组 | 3 | \ | \ | \ | \ | \ |
解决待定数组:将数字按从大到小一个一个取出来,再将他们从右往左填入空格子,得到:
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
goal[]goal[]goal[] | 1 | 3 | 2 | 4 | 5 | 6 |
待定数组 | \ | \ | \ | \ | \ | \ |
最后,在 midmidmid 个回合之后,得到的数组就是 goal={1,3,2,4,5,6}goal=\{1,3,2,4,5,6\}goal={1,3,2,4,5,6} 了。
但是,我们为什么要通过
将数字按从大到小一个一个取出来,再将他们从右往左填入空格子
这样的操作顺序来填入 goal[]goal[]goal[] 中?
其实很简单,我们考虑 t[i]<midt[i] < midt[i]<mid 即这个数左边已经没有比它大的了。
为了保证我们填进去之后满足这些待定数字的左边都没有比他们更大的,而采用这种方法。
最后,我们还需要暴力进行 K−calc(mid)K-calc(mid)K−calc(mid) 次的 swapswapswap 操作才得到真正的 goalgoalgoal 数组。
代码实现不必多说
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define pii pair<int,int>
#define Endl putchar('\n')
#define FILEOI
#define int long long
#ifdef FILEOI
#define MAXBUFFERSIZE 500000
inline char fgetc(){
static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
}
#undef MAXBUFFERSIZE
#define cg (c=fgetc())
#else
#define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
inline int qread(){
int x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}
const int MAXN=1e6;
int N,K,tot,a[MAXN+5],tmp[MAXN+5],t[MAXN+5],BIT[MAXN+5];
inline int lowbit(const int i){return i&(-i);}
inline void msort(int a[],const int l,const int r){
if(l==r)return;
int mid=(l+r)>>1;
msort(a,l,mid);
msort(a,mid+1,r);
int p1=l,p2=mid+1,ptr=l-1;
while(p1<=mid && p2<=r){
if(a[p1]<a[p2])t[++ptr]=a[p1++];
else{
tot+=(mid-p1+1);
t[++ptr]=a[p2++];
}
}
while(p1<=mid)t[++ptr]=a[p1++];
while(p2<=r)t[++ptr]=a[p2++];
rep(i,l,r)a[i]=t[i];
}
inline int calc(const int x){
int ret=0;
rep(i,1,N)ret+=Min(t[i],x);
return ret;
}
inline int bisearch(){
int l=0,r=N,mid,ret;
while(l<=r){
mid=(l+r)>>1;
if(calc(mid)>K)r=mid-1;
else l=mid+1,ret=mid;
}
return ret;
}
inline void update(const int p){
for(int i=p;i<=N;i+=lowbit(i))++BIT[i];
}
inline int query(const int p){
int ret=0;
for(int i=p;i;i-=lowbit(i))ret+=BIT[i];
return ret;
}
int ans[MAXN+5];
signed main(){
#ifdef FILEOI
freopen("sort.in","r",stdin);
freopen("sort.out","w",stdout);
#endif
qread(N,K);
rep(i,1,N)tmp[i]=a[i]=qread();
msort(tmp,1,N);
if(tot<K)return puts("Impossible!"),0;
for(int i=1;i<=N;++i){
t[i]=i-query(a[i])-1;
update(a[i]);
// writc(t[i],' ');
}
// Endl;
int x=bisearch();
vector<int>buffer;
rep(i,1,N)if(t[i]>=x)ans[i-x]=a[i];
else buffer.push_back(a[i]);
sort(buffer.begin(),buffer.end());
fep(i,N,1)if(!ans[i]){
ans[i]=buffer.back();
buffer.pop_back();
}
K-=calc(x);
rep(i,1,N-1)if(ans[i]>ans[i+1]){
if(K==0)break;
swap(ans[i],ans[i+1]);
--K;
}
rep(i,1,N)writc(ans[i],' ');
return 0;
}