题意:
求冒泡排序算法的“趟数”
思路:
一趟冒泡排序可以可以还原一个数的一个逆序对,那么我们需要求出一个数最多的逆序对个数就是冒泡的趟数。
拿样例来举个例子:
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;
}