題意:
求冒泡排序算法的“趟數”
思路:
一趟冒泡排序可以可以還原一個數的一個逆序對,那麼我們需要求出一個數最多的逆序對個數就是冒泡的趟數。
拿樣例來舉個例子:
5
1
5
3
8
2
有逆序對(5,3)(5,2)(3,2)(8,2) 其中逆序對最多的是 2
第一趟冒泡以後 變成 1 3 5 2 8 消除了(5,3)和(8,2)
第二趟冒泡以後 變成 1 3 2 5 8 消除了(5,2)
第三趟冒泡以後 變成 1 2 3 5 8 消除了(3,2)
除了樸素求逆序對,常見的求逆序對主要有兩種方法。
- 歸併排序求逆序對
- 樹狀數組求逆序對
此題用歸併排序的方法無法確認最多的逆序對個數(也可能存在,但是我不會…)
所以採用樹狀數組求逆序對,順便記錄一下樹狀數組求逆序對的原理:
衆所周知,樹狀數組主要是用來維護前綴和,所以我們要把求逆序對的問題轉換成一個前綴和問題。
步驟:
- 先將數列排序
- 然後按原序列的順序,把數列中的數插入到樹狀數組中。在插入數的時候,將已經插入的數所在的位置 ,打一個標記值 設,每次拿已經插入的數字個數 - 的前 項和 把其中的差累加就是 逆序對的個數。
拿樣例來舉個例子:
-
第一次插入 ,在下標 下面打個標記 1,共插入個數,前 個數的前綴和爲
-
第二次插入 ,在下標 下面打個標記 1,共插入個數,前 個數的前綴和爲
-
第三次插入 ,在下標 下面打個標記 1,共插入個數,前 個數的前綴和爲
-
第四次插入 ,在下標 下面打個標記 1,共插入個數,前 個數的前綴和爲
-
第五次插入 ,在下標 下面打個標記 1,共插入個數,前 個數的前綴和爲
共4個逆序對。
原理:
注:是排好序的數列。
因爲插入的順序是原數組的順序,但是標記按排好序的順序打的,這完全符合逆序對的定義:假設在原數組中 排在 前面,我們先插入 處打上標記,然後再插入 處打上標記, 此時如果, 在 的前面 ,我們統計前 個數的前綴和是可以統計到 的,相反如果, 就在 的後面,我們就統計不到,所以在 前面插入 ,又大於的數就可以和 構成逆序對。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int N = 1e5+7;
int n,tree[N];
int ans;
void add(int k,int num)
{
while(k<=n)
{
tree[k]+=num;
k+=k&-k;
}
}
int read(int k)
{
int sum=0;
while(k)
{
sum+=tree[k];
k-=k&-k;
}
return sum;
}
struct node
{
int val,pos;
}a[N];
bool cmp(node a,node b)
{
return a.val < b.val;
}
int main(void)
{
//freopen("data.in","r",stdin);
int i,j;
int b[N];
while(scanf("%d",&n)==1)
{
memset(tree,0,sizeof(tree));
for(i=1;i<=n;i++)
{
scanf("%d",&a[i].val);
a[i].pos = i;
}
sort(a+1,a+1+n,cmp);
int cnt = 1;
for(i=1;i<=n;i++)
{
if(i != 1 && a[i].val != a[i-1].val)
cnt++;
b[a[i].pos] = cnt;
}
for(i=1;i<=n;i++)
{
add(b[i],1);
ans = max(ans,i - read(b[i]));
}
printf("%lld\n",ans+1);
}
return 0;
}