USACO2018有三道关于冒泡排序的题,做完感觉排序白学了。
1.[USACO18OPEN]Out of Sorts S
sorted = false while (not sorted): sorted = true moo for i = 0 to N-2: if A[i+1] < A[i]: swap A[i], A[i+1] sorted = false
给出数列,问这个循环会执行多少次。
通过观察,我们能够轻易得出,在每次执行冒泡时,大数会很快跑到后面去,小数一次只能前进一次。(不明白手玩)。
所以答案就是每个当前位置减去排序后位置去max。
Code
#include<iostream> #include<cstdio> #include<algorithm> #define N 100002 using namespace std; int ans,n; struct zzh{ int a,b; }a[N]; bool cmp(zzh x,zzh y){ if(x.a!=y.a)return x.a<y.a; else return x.b<y.b; } int main(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i].a),a[i].b=i; sort(a+1,a+n+1,cmp); for(int i=1;i<=n;++i) ans=max(ans,a[i].b-i+1); cout<<ans; return 0; }
2.[USACO18OPEN]Out of Sorts G
sorted = false while (not sorted): sorted = true moo for i = 0 to N-2: if A[i+1] < A[i]: swap A[i], A[i+1] for i = N-2 downto 0: if A[i+1] < A[i]: swap A[i], A[i+1] for i = 0 to N-2: if A[i+1] < A[i]: sorted = false
把冒泡排序改了一下,问会进行几次循环。
冒泡排序的缺点就是小数前进的很慢,这个代码每次正向冒泡一遍,在反着扫一遍,看似把这个问题解决了。
看起来计算会比较复杂,不如换一种思考方式。
我们把每个数映射为1-n中的每个数,排完序后肯定是a[i]=i。
在定义一个东西叫分割点,一个位置i成为分割点,当且仅当1-i中所有数值域为1-i。
这样区域之后就不会和其他区域交换了。
当每个数都是分割点时,我们的排序就完成了。
考虑一个点i,设1-i中大于i的数有x个,如果我们用普通的冒泡排序,一轮之后x中一定会有一个数被换出去,还有一个数被换了进来。
但因为算法的局限性,我们不能保证换进来的数是1-i中的。
但双向冒泡排序就避免了这个问题,一旦有一个“非法”的数被换了进来,在反向扫描时会被一个“合法”的数换掉。
这样每次冒泡不合法的数就减少了一。
这样统计答案变得轻而易举。
Code
#include<iostream> #include<cstdio> #include<algorithm> #define N 100002 using namespace std; int n,tr[N],ans; struct zzh{ int a,b; }a[N]; bool cmp(zzh a,zzh b){ if(a.a!=b.a)return a.a<b.a; else return a.b<b.b; } inline void add(int x,int y){while(x<=n)tr[x]+=y,x+=x&-x;} inline int q(int x){int ans=0;while(x)ans+=tr[x],x-=x&-x;return ans;} int main(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i].a),a[i].b=i; sort(a+1,a+n+1,cmp); ans=1; for(int i=1;i<=n;++i){ add(a[i].b,1); ans=max(ans,i-q(i)); } cout<<ans; return 0; }
3.[USACO18OPEN]Out of Sorts G
quickish_sort (A) { if length(A) = 1, return do { // Main loop work_counter = work_counter + length(A) bubble_sort_pass(A) } while (no partition points exist in A) divide A at all partition points; recursively quickish_sort each piece }
这哥们在快速排序里面套了个冒泡排序。
每次冒泡的贡献是冒泡序列的长度。
循环退出的条件是存在分割线,分割线的定义:一个点成为分割点,当且仅当前缀最大值小于后缀最小值。
那么左右两个区间就没有关系了。
这题的思路是统计没个元素的贡献,最后求和。
当一个元素没有贡献时,它的长度为一(不然会被继续冒泡)。
所以他的贡献为它的前面成为分割线的时间和后面成为分割线的时间取max。
那我们就计算每个位置成为分割线的时间。
然后突然发现好像回到了上一题。
唯一不同的是这是普通的冒泡排序,每个小数每次只能前进1个单位。
所以每个分界线的答案就是max(pos[1-i])-i;
Code
#include<iostream> #include<cstdio> #include<algorithm> #define N 100002 using namespace std; int n,t[N],qmax; long long ans; struct zzh{ int a,b; }a[N]; bool cmp(zzh a,zzh b){ if(a.a!=b.a)return a.a<b.a; else return a.b<b.b; } int main(){ scanf("%d",&n); for(int i=1;i<=n;++i)scanf("%d",&a[i].a),a[i].b=i; sort(a+1,a+n+1,cmp); for(int i=1;i<=n;++i){ qmax=max(qmax,a[i].b); t[i]=qmax-i; } for(int i=1;i<=n;++i){ int x=max(t[i],t[i-1]); if(!x)x++; ans+=x; } cout<<ans; return 0; }