[ 题解 ] [ 排序 ] F. Out of Sorts II
http://codeforces.com/group/NVaJtLaLjS/contest/238202/problem/F
这是初级版的链接:https://www.cnblogs.com/Kaidora/p/10537183.html
题意:
还是冒泡排序,不过这个冒泡是双向冒泡,循环一来回一个moo。
同样问N个数据排序后有几个moo。
这个双向冒泡有点特别。首先我们看看单向冒泡的特点:
8 5 3 2 5 3 2 8 3 2 5 8 2 3 5 8
如你所见,第一次循环最大值就到了最末尾,第n次循环,第n大的值就会被移到倒数n位。
对于双向冒泡排序:
8 5 3 2 5 3 2 8 & 2 5 3 8 2 3 5 8
第一次循环,最大值到最末尾,最小值到最首位,也就是说,第n大值移到倒数n位,第n小值移到第n位。
可以说,在一轮循环中,双向冒泡完成了单向排序的两次操作。
那么,是不是把只要把单向版的答案/2就可以交上去?
我试过了,直接GG。请看Test 2数据:
10 7 7 7 7 7 6 6 6 6 6
单向的话,把5个7拉到末尾,答案5+1;
双向的话,每次交换前后一对7与6,答案是5。
如果是10个不同数据,理论上确实是单向冒泡的一半次数;
但是这个样例中,用单向冒泡,5个6之间并没有打乱过,而双向冒泡硬是把5个6逐个拉到前面,两种冒泡都需要5次循环。
为什么会这样?
在一轮循环中,双向冒泡确实相当于做了两次单向冒泡,不过这其中必须是不同的数据;
这种情况下需要对计数去重;
如果有一对相同的数据,那么有一次循环就只做了一次操作,这种情况不需要去重。
我暂时还没办法详细地把计数和去重规则描述出来,这是大佬的代码,我的代码与此基本类似:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstdlib> 6 #include<queue> 7 #include<vector> 8 typedef long long ll; 9 using namespace std; 10 struct node{ 11 int val,id; 12 }a[100005]; 13 bool cmp(node a,node b) 14 { 15 if(a.val==b.val)return a.id<b.id; 16 return a.val<b.val; 17 } 18 int vis[100005]; 19 int main() 20 { 21 /* 22 这次的冒泡排序改变了,用了双向冒泡排序 23 每次双向冒泡排序,会把一个后面的数放到前面,一个前面的数放到后面 。 24 先把序列排序,然后对于每个位置的数 25 看他前面的数有多少个到后面去了,然后去重 ,取最大值就行了 26 */ 27 28 /* 29 对每个排序了的数,对原先的位置进行标记,如果当前的数原先在后面而现在在原先位置之前,说明需要排序,次数加1 30 不过,如果这个数排序后的位置,是之前的数目曾经标记过的, 31 也就是说排序后的当前的数的位置,是排序后位于这个数前面的数在原先未排序时的位置,那么,需要把次数减1. 32 */ 33 int n; 34 scanf("%d",&n); 35 for(int i=1;i<=n;i++) 36 { 37 scanf("%d",&a[i].val); 38 a[i].id=i; 39 } 40 sort(a+1,a+1+n,cmp); 41 //旧坐标是id,新坐标是对应的i 42 int cnt=0,ans=1;//cnt用来对排序后应该在i右边,而实际上在i左边的元素进行计数 43 for(int i=1;i<=n;i++) 44 { 45 if(a[i].id>i)//如果旧坐标在排序后的坐标之后 46 cnt++; 47 if(vis[i])//双向冒泡两数交换位置算一次排序,所以注意去重 48 cnt--; 49 vis[a[i].id]=1;//对于所处的旧坐标进行标记 50 ans=max(cnt,ans); 51 } 52 printf("%d\n",ans); 53 return 0; 54 }
以后再慢慢完善。