冒泡排序深入理解

对于冒泡排序有一个小性质: 每一次都会把序列未排好序的最大数"沉底", 即推到序列尾部

1.P4378 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

题意即进行多少次冒泡排序

对于一个序列, 我们称之为有序的, 当且仅当对于任意一个位置前面没有比它大的数(可以模拟一下)

比如:6 1 2 3 4 5 进行一次为 1 2 3 4 5 6

那么对于位置i, 冒泡排序进行到i-1时, $a_{i-1}$为前i1个数中最大的一个, 如果它大于$a_i$那么它就会到$a_i$的后面

由此可推知, 每一次位置i前都会将一个比$a_i$大的数推至其后, 直至没有比它大的

那么我们对每位置求一下它前面有几个比它大就好啦(注意要将答案加一)

具体来说先进行离散化, 再树状数组求解即可

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100500;
int d[N], n;
int read(void) {
    int x = 0;
    char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)){
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    return x;
}
struct node{
    int val, pos;
    bool operator < (const node &i) const{
        if (val == i.val) return pos < i.pos;
        return val < i.val;
    }
}p[N];
inline int low(int x) {
    return x & -x;
}
int get(int x) {
    int tmp = 0;
    for (;x;x -= low(x)) tmp += d[x];
    return tmp;
}
void add(int x) {
    for (;x <= n; x += low(x)) d[x]++;
}
bool cmp(node i,node j) {
    return i.pos < j.pos;
}
int main() {
    n = read();
    for (int i = 1;i <= n; i++) p[i] = (node){read(),i};
    sort(p + 1,p + n + 1);
    for (int i = 1;i <= n; i++) p[i].val = i;
    sort(p + 1,p + n + 1, cmp);
    int ans = 0;
    for (int i = 1;i <= n; i++) {
        add(p[i].val);
        ans = max(ans, i - get(p[i].val));
    }
    printf ("%d\n", ans+1);
    return 0;
}

2.P4375 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

题意:求双向冒泡排序的排序次数

对于一个序列, 我们称之为有序的, 当且仅当对于任意一个位置前面没有比它大的数(可以模拟一下)

我们暂且称它为平衡条件吧

首先将序列离散化

相比较于Out of Sorts S, 本题思路在于不动的位置, 结论为对于位置x, ans = max{ans, 前面有几个数的数值大于x}

为什么呢

在x不满足平衡条件的时候

首先第一波操作的时候,对于前x个位置一定会换出一个大于x的数

因为它不满足平衡条件

第二波操作时, 又会有一个小于等于x的数插回来

因为回来的时候一定会冒泡出一个位置在x后的最小值, 因为x不满足平衡条件, 所以最小值小于等于x, 就又插了回来

有人可能会问为什么Out of Sorts S不能用这个式子嘞, 因为每次换出的一定大于x, 但x+1位置上的数可能换过来, 而它有可能大于x

由此可知, 求每个位置前大于其的数就行啦

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100500;
int d[N], n;
int read(void) {
    int x = 0;
    char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)){
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    return x;
}
struct node{
    int val, pos;
    bool operator < (const node &i) const{
        if (val == i.val) return pos < i.pos;
        return val < i.val;
    }
}p[N];
inline int low(int x) {
    return x & -x;
}
int get(int x) {
    int tmp = 0;
    for (;x;x -= low(x)) tmp += d[x];
    return tmp;
}
void add(int x) {
    for (;x <= n; x += low(x)) d[x]++;
}
bool cmp(node i,node j) {
    return i.pos < j.pos;
}
int main() {
    n = read();
    for (int i = 1;i <= n; i++) p[i] = (node){read(),i};
    sort(p + 1,p + n + 1);
    for (int i = 1;i <= n; i++) p[i].val = i;
    sort(p + 1,p + n + 1, cmp);
    int ans = 1;
    for (int i = 1;i <= n; i++) {
        add(p[i].val);
        ans = max(ans, i - get(i));
    }
    printf ("%d\n", ans);
    return 0;
}
/*
6
2 5 6 3 1 4

*/

3.P4372 Out of Sorts P

bubble_sort_pass (A) {
   for i = 0 to length(A)-2
      if A[i] > A[i+1], swap A[i] and A[i+1]
}
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
}

这道题用到了一个套路, 就是"横向变纵向"

求每一次冒泡排序的长度, 不如求每一个点被冒泡排序了几次

定义分割点为i与i+1的分割线,不妨假设它就在i上吧

再次定义序列排好序的标准

我们称一个序列是有序的当且仅当所有点(除了n)都是分割点

那么接下来我们要求分割点的出现时间t数组

为什么求:

对于每个点它不用在进行冒泡排序了当且仅当两边都已成为分割点, 也就是两边出现时间的最大值

依据t数组,我们可以求出每个点被排了几次

怎么求(敲重点):

首先离散化

对于一个点x来说, 所有小于它的数却在它后面的, 每一次都会向前走一次

那么它出现的时间就是离它最远的小于它的点冒泡到它前面的时间

即那个点到它的距离, 具体见代码

所以单调队列或指针都可以维护

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 100500;
int d[N], n;
int read(void) {
    int x = 0;
    char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)){
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    return x;
}
struct node{
    int val, pos;
    bool operator < (const node &i) const{
        if (val == i.val) return pos < i.pos;
        return val < i.val;
    }
}p[N];
bool cmp(node i,node j) {
    return i.pos < j.pos;
}
int t[N], k;
int main() {
//  freopen("hs.in","r",stdin);
    n = read();
    for (int i = 1;i <= n; i++) p[i] = (node){read(),i};
    sort(p + 1,p + n + 1);
    for (int i = 1;i <= n; i++) p[i].val = i;
    sort(p + 1,p + n + 1, cmp);
    long long ans = 0;
    k = n;
    for (int i = n;i >= 1; i--) {
        while (p[k].val > i) k--;
        t[i] = max(p[k].pos - i, 1);
    }
    for (int i = 0;i < n; i++) ans += max(t[i], t[i+1]);
    printf ("%lld\n", ans);
    return 0;
}
/*
6
2 5 6 3 1 4

*/

4.T99343 奇怪的排序

这道题来源于一位数竞大佬提供的灵感

再次定义一个序列有序

我们称一个序列是有序的,当且仅当它的逆序对数为0或n*(n-1)/2;

引理1: 交换序列中相邻的两个数会改变原序列逆序对个数的奇偶性

引理2: 将序列相邻两个数插入别处不会改变原序列逆序对个数的奇偶性

​ 证明: a~1~...a~i~a~j~...a~q~...a~n~ 不断将a~j~与它右边的数字交换直至正好换到a~q~ 即a~1~...a~j~a~i~...a~n~ 此时共交换了q - j 次

​ 再将a~i~ 向右与相邻数字交换q-1-i次到$a_j$左侧 ,此时共交换2 * (q - j) 次,为偶数次,所以奇偶性不变

那么说明逆序对数与排序好的逆序对数奇偶性不同时不能满足要求

下面证明相同时可以满足要求

以正序为例, 每次将序列最小的数和后面的数插到已排序部分的后面, 如果最小数在最后时就将后2,3个数插在它后面

当未排序列只剩两个数时, 逆序对个数也一定是偶数, 只可能是0

即序列有序, 证毕

具体实现是讨论一下n*(n-1)/2的奇偶性, 并树状数组求出原序列逆序对个数

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
const int M = 500005;
using namespace std;
int n, sum[M];
struct Num{
    int val,num;
    inline friend bool operator < (Num a,Num b){
        return a.val > b.val;
    }
}p[M];
inline int lowbit(int x){
    return x&-x;
}
void add(int k,int x){
    while(k<=n){
        sum[k]+=x;
        k+=lowbit(k);
    }
}
int getsum(int k){
    int tmp=0;
    while(k>0){
        tmp+=sum[k];
        k-=lowbit(k);
    }
    return tmp;
}
long long Ans=0;
char ss[1<<17],*A=ss,*B=ss;
inline char gc()
{if(A==B){B=(A=ss)+fread(ss,1,1<<17,stdin);if(A==B)return EOF;}return*A++;}
template<class T>inline void read(T&x){
    cin >> x ;
}
int main(){
    int t;
    read(t);
    while(t--) {
        Ans = 0;
        memset(sum, 0, sizeof(sum));
        read(n);
        for(int i=1;i<=n;i++){
            read(p[i].val);
            p[i].num=i;
        }
        sort(p+1,p+n+1);
        for(int i=1;i<=n;i++){
            add(p[i].num,1);
            Ans+=getsum(p[i].num-1);
        }
//      printf ("%lld\n", Ans);
        if (n % 4 > 1)
            printf("Yes\n");
        else if (Ans % 2 == 1)
            printf("No\n");
        else
            printf("Yes\n");
    }
    return 0;
}
10-06 05:47