题目勾起了我对我蒟蒻时代的回忆,虽然我现在也蒟蒻

题目

点这里

可能链接会挂,在网上搜题目就有。

毕竟 \(BZOJ\) 有点老了...

考场思考

本来以为十分友善的一道题...哎...

考试的时候这样想的:

定义 \(ptr[i]\) 表示从第 \(i\) 位开始,往右边遇到的第一个大于 \(a[i]\) 的数的下边。

考虑每次一轮就是把 \(a[i]\) 放到 \(ptr[i]-1\) 的位置,这样一共需要 \(ptr[i]-1-i\) 次 \(swap\) 操作的机会。

这样做似乎是可行的,但是维护 \(ptr\) 数组太难了,太难了...

然后不得不拿个 \(30pts\) 的暴力分,无奈 \(AFO\) ...

正解

这居然是道二分,道二分!!!

首先,我们考虑冒泡排序的本质是什么:

先看一看普通的冒泡排序代码: 没错就是题目中的

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]);

然后,我们展开分析

  • 对于外层循环一圈结束之后,第 \(i\) 大的数会跑到最后边去,同时也交换了路上的某些点,为了方便表示,我们把外层循环一边成为一个回合
  • 内层循环中 \(j\) 指向的数字(即 \(a[j]\) )一定是 \(j\) 遍历到的最大的数,即若 \(a[j]<a[j+1]\) ,那么 \(j\) 会换一个指向对象,从 \(a[j]\) 指向 \(a[j+1]\) ,反之,如果 \(a[j]>a[j+1]\) ,那么 \(j\) 会保持指向 \(a[j]\) 。
  • 一个数能往左走,当且仅当它的左边有某一个数比它大。
  • 反之,一个数如果左边有比它大的数,那么这个回合它一定会往左走。

我们再考虑一个东西:每一个回合,除这个序列已经有序的情况外,是一定存在交换操作的。

那么,当我们的回合数 \(x\) 增加时,操作数 \(opt\) 也是会增加的。

有了这个,自然而然考虑二分。

怎么二分?考虑二分回合数 \(mid\) 。

有了 \(mid\) ,只需要计算一下当我们经过 \(mid\) 个回合之后,使用了多少次 \(swap\) 。

怎么算呢?考虑我们刚刚分析的规律:

  • 一个数能往左走,当且仅当它的左边有某一个数比它大。
  • 反之,一个数如果左边有比它大的数,那么这个回合它一定会往左走。

我们记位置为 \(i\) 的数 \(a[i]\) 的左边有 \(t[i]\) 个比它大的。

那么,我们考虑,如果满足 \(t[i]>0\) ,那么每进行完一个回合, \(t[i]\) 应该是减小 \(1\) ,同时 \(a[i]\) 应该往左移动一个单位,而往左移动一格,也就是提供了一次 \(swap\) 的使用。

也就是说,如果 \(mid\le t[i]\) ,那么进行完 \(mid\) 个回合之后, \(a[i]\) 的左边仍然有比它大的,但同时 \(a[i]\) 也往左边移动了 \(mid\) 位,而且它提供了 \(mid\) 次 \(swap\) 的使用次数。

但是,如果 \(t[i]<mid\) 呢?

这就说明,对于 \(mid\) 个回合之后的 \(a[i]\) ,从它的位置往左数,已经没有数比它大了。

假设这时 \(a[i]\) 的位置是 \(p\) ,那么满足 \(\forall j\in [1,p),a[i]>a[j]\) 。

而且,这个时候, \(a[i]\) 已经没有再往左走的机会了。

也就是说,如果对于 \(a[i]\) 结束时的目标点 \(p'\) ,如果 \(p'<p\) ,那么 \(a[i]\) 就永远也没机会到它原本的位置了。

那么我们可以确定, \(p\le p'\le N\) 。

然后,不幸的消息出现了,如果我们再这样推下去,推到后面,就推不动了,然后 \(++ZiBi\) ...

具体为什么,可以去试一下,嘿嘿嘿...

正确地分析一下此题 (来自于 \(lj\) 大佬)

首先继承一下之前的烂尾分析:

  • 当 \(mid\le t[i]\) 时,这个数使用了 \(mid\) 次 \(swap\)
  • 当 \(t[i]<mid\) 时,这个数使用了 \(t[i]\) 次 \(swap\)

那么,对于一个回合数 \(mid\) ,我们可以 \(O(N)\) 地扫描一遍,得到它在 \(mid\) 回合后,使用的 \(swap\) 的次数 \(tot\) 。

那么,当 \(tot>K\) 时,显然这个 \(mid\) 不合法。

记 \(calc(x)\) 为 \(x\) 回合后,使用的 \(swap\) 次数。

否则,我们就去找 \(\forall mid,max\{calc(mid)|calc(mid)\le K\}\) 。

用人话说就是,找到所有的 \(mid\) 中,使得 \(calc(mid)\) 最接近 \(K\) 但又比 \(K\) 小。

显然这是可以用二分做到的。

找到这个 \(mid\) 有什么用呢?我们可以保证在 \(mid\) 个回合后,如果再来一个回合,会使得一共使用的 \(swap\) 次数大于 \(K\) ,而我们还剩下的 \(K-calc(mid)\) 次交换,可以再用一个 \(O(N)\) 来暴力解决。

接下来考虑我们已经得到了 \(mid\) ,那么怎么计算进行完 \(mid\) 个回合之后,每个数的位置。

对于一个数 \(a[i]\) ,如果 \(mid\le t[i]\) ,那么这个数往左移动 \(mid\) 个位置。

但是,如果 \(t[i]<mid\) ,也就是说它不会一直往左移动,还有可能往右移动,这个时候怎么解决?

我们用一个例子来说明:

假定我们有一个序列:

\(N=6,arr=\{3,5,1,6,2,4\}\)

假设 \(mid=2\) ,最终的数组为 \(goal[]\) , 那么操作过程如下:


循环第一遍: \(i=6\) ( \(i\) 从大到小遍历, \(i\) 表示数值,而非位置)

因为 \(mid=2\) ,所以前 \(2\) 大的数都到了目标位置。

下标 1 2 3 4 5 6
\(goal[]\) 6

循环第二遍: \(i=5\)

下标 1 2 3 4 5 6
\(goal[]\) 5 6

循环第三遍: \(i=4\) ,而 \(t[6]==2\) (数字 \(4\) 位置在 \(6\) )

因为 \(t[6]\le mid\) ,所以数字 \(4\) 从它原来的位置 \(6\) 往左移动 \(2\) ,到位置 \(4\) ,那么

下标 1 2 3 4 5 6
\(goal[]\) 4 5 6

循环第四遍: \(i=3\) ,而 \(t[1]=0\) 。

此刻 \(mid>t[1]\) ,让这个数字待定

此刻的数组:

下标 1 2 3 4 5 6
\(goal[]\) 4 5 6
待定数组 3 \ \ \ \ \

循环第五遍: \(i=2\) ,而 \(t[5]=3\) 。

此刻 \(mid\le t[5]\) ,这个数字往左移动 \(mid\) 位,那么:

下标 1 2 3 4 5 6
\(goal[]\) 2 4 5 6
待定数组 3 \ \ \ \ \

循环第六遍: \(i=1\) ,而 \(t[3]=2\) 。

此刻 \(mid\le t[3]\) ,所以这个数字往左移动 \(mid\) 位,那么

下标 1 2 3 4 5 6
\(goal[]\) 1 2 4 5 6
待定数组 3 \ \ \ \ \

解决待定数组:将数字按从大到小一个一个取出来,再将他们从右往左填入空格子,得到:

下标 1 2 3 4 5 6
\(goal[]\) 1 3 2 4 5 6
待定数组 \ \ \ \ \ \

最后,在 \(mid\) 个回合之后,得到的数组就是 \(goal=\{1,3,2,4,5,6\}\) 了。

但是,我们为什么要通过

将数字按从大到小一个一个取出来,再将他们从右往左填入空格子

这样的操作顺序来填入 \(goal[]\) 中?

其实很简单,我们考虑 \(t[i] < mid\) 即这个数左边已经没有比它大的了。

为了保证我们填进去之后满足这些待定数字的左边都没有比他们更大的,而采用这种方法。

最后,我们还需要暴力进行 \(K-calc(mid)\) 次的 \(swap\) 操作才得到真正的 \(goal\) 数组。

代码实现不必多说 其实是我懒得打...

代码待补...

#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;
}

「题解」「2014 NOI模拟赛 Day7」冒泡排序的更多相关文章

  1. 「Vijos 1284」「OIBH杯NOIP2006第二次模拟赛」佳佳的魔法阵

    佳佳的魔法阵 背景 也许是为了捕捉猎物(捕捉MM?),也许是因为其它原因,总之,佳佳准备设计一个魔法阵.而设计魔法阵涉及到的最关键问题,似乎就是那些带有魔力的宝石的摆放-- 描述 魔法阵是一个\(n ...

  2. NOI模拟赛 Day1

    [考完试不想说话系列] 他们都会做呢QAQ 我毛线也不会呢QAQ 悲伤ING 考试问题: 1.感觉不是很清醒,有点困╯﹏╰ 2.为啥总不按照计划来!!! 3.脑洞在哪里 4.把模拟赛当作真正的比赛,紧 ...

  3. 6.28 NOI模拟赛 好题 状压dp 随机化

    算是一道比较新颖的题目 尽管好像是两年前的省选模拟赛题目.. 对于20%的分数 可以进行爆搜,对于另外20%的数据 因为k很小所以考虑上状压dp. 观察最后答案是一个连通块 从而可以发现这个连通块必然 ...

  4. 「题解」:07.18NOIP模拟赛T1:星际旅行

    问题 A: 星际旅行 时间限制: 1 Sec  内存限制: 256 MB 题面 题面谢绝公开. 考试心路历程 拿到这道题感觉很懵逼,所以先搞的T2和T3,最后码了个暴力,结果还不如直接输出‘0’得分高 ...

  5. Solution -「NOI 模拟赛」彩色挂饰

    \(\mathcal{Description}\)   给定一个含 \(n\) 个点 \(m\) 条边的简单无向图,设图中最大点双的大小为 \(s\),则保证 \(s\le6\).你将要用 \(k\) ...

  6. Solution -「NOI 模拟赛」出题人

    \(\mathcal{Description}\)   给定 \(\{a_n\}\),求一个 \(\{b_{n-1}\}\),使得 \(\forall x\in\{a_n\},\exists i,j\ ...

  7. 「模拟赛20190327」 第二题 DP+决策单调性优化

    题目描述 小火车虽然很穷,但是他还是得送礼物给妹子,所以他前往了二次元寻找不需要钱的礼物. 小火车准备玩玩二次元的游戏,游戏当然是在一个二维网格中展开的,网格大小是\(n\times m\)的,某些格 ...

  8. 「模拟赛20180306」回忆树 memory LCA+KMP+AC自动机+树状数组

    题目描述 回忆树是一棵树,树边上有小写字母. 一次回忆是这样的:你想起过往,触及心底--唔,不对,我们要说题目. 这题中我们认为回忆是这样的:给定 \(2\) 个点 \(u,v\) (\(u\) 可能 ...

  9. 「模拟赛20181025」御风剑术 博弈论+DP简单优化

    题目描述 Yasuo 和Riven对一排\(n\)个假人开始练习.斩杀第\(i\)个假人会得到\(c_i\)个精粹.双方轮流出招,他们在练习中互相学习,所以他们的剑术越来越强.基于对方上一次斩杀的假人 ...

随机推荐

  1. zabbix监控规划及实施

    一.规划监控拓扑 二.主机分组 例:交换机.Nginx.Tomcat.MySQL 三.监控对象识别: 1.使用SNMP监控交换机 a.交换机开启snmp config -t snmp-server c ...

  2. ansible笔记(11):tags的用法

    你写了一个很长的playbook,其中有很多的任务,这并没有什么问题,不过在实际使用这个剧本时,你可能只是想要执行其中的一部分任务而已,或者,你只想要执行其中一类任务而已,而并非想要执行整个剧本中的全 ...

  3. AAC huffman decoding

    在AAC编码器内部,使用huffman coding用于进一步减少scalefactor和量化频谱系数的冗余. 从individual_channel_stream层提取码流进行huffman解码,码 ...

  4. 【Python】解决浮点数间运算存在不确定尾数的问题

    #浮点数间运算存在不确定尾数,所以会输出False if 0.1+0.2==0.3: print("Ture\n") else: print("False\n" ...

  5. (c#)删除链表中的节点

    题目 解: 解析: n1→n2→n3→n4删除n2即将n2更改成n3n1→n3(n2)→n4

  6. winform学习(5)MDI窗体

    SDI窗体 single document interface 单文档界面,即单个简单的窗体 MDI窗体 multiple document interface 多文档界面(主窗体与子窗体的关系,避免 ...

  7. jmeter的使用---控制器

    1.如果(If)控制器.Switch Controller if控制语句,判断字段是否存在,或者符合,执行不同的逻辑 2.简单控制器 一次进件流程,需要不同模块的数据,例如登陆,提交个人信息,信用认证 ...

  8. EnumSet

    这个概念是在 Effective Java中了解到的, 可以通过EnumSet来代替位域这种方式表达.并不是很常见的概念, 因此记录下.如果在这之前恰好了解过 bitmap这种数据结构就更好了.不了解 ...

  9. python eval() 进行条件匹配

    最近开发一个功能,根据条件表达式过滤数据,其中用到了eval(条件字符串,字典) 发现一个现象: >>> print u"campGrade in [ '\u51cf\u8 ...

  10. Spring 属性依赖注入

    1.1    属性依赖注入 依赖注入方式:手动装配 和 自动装配 手动装配:一般进行配置信息都采用手动 基于xml装配:构造方法.setter方法 基于注解装配: 自动装配:struts和spring ...