冒泡排序深入理解
对于冒泡排序有一个小性质: 每一次都会把序列未排好序的最大数"沉底", 即推到序列尾部
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;
}