题目描述
链接:https://vjudge.net/problem/POJ-2299
题意:给你一组数,你能做的操作是交换两个相邻的数,令这个数列变成递增的,要求最小操作数。
思路分析
说实话,看到这样的题,感觉我已经本能反应地想到了逆序数了。。不知道是不是最近看了点线代的缘故。。
具体分析起来是这样的:
5
9 1 0 5 4
以样例为例,我们一个一个的输入,那么输入到1的时候,9和1是逆序对,需要交换一次。输入,变成1 9,输入0,0和9是逆序对,需要交换,变成1 0 9,0和1又是逆序对,变成0 1 9。感受到没有?
1往前的和1组成的逆序对有9 1这一对,0往前的和0组成的逆序对有9 0,1 0这两对……这不就变成了求逆序数了吗?
确定了它原本的问题之后,我们就要想方法解决。之前接触过归并排序计算的方法,这次就尝试了用树状数组。首先,怕数与数之间差距太大造成浪费,需要离散化。离散化后为1~n之间的数。之后,需要想怎么计算逆序数。
我们知道,树状数组的一个基本操作就是求前缀和,我们可以利用这个来实现:每次插入一个数ai之后,我们可以计算一下目前树里边处于1~ai的值一共有多少个。假设说这个序列刚好是递增的,那么,1 ~ai的个数应该刚好是ai。首先,这个个数是不可能超过ai的(对于3顶多也就1 2放在它前面,不可能再出现一个正整数比3小的),如果这个数小于ai,那就说明有的数现在还没有被加到树上(比如:1 3,前缀和sum(3)=2,少了一个2),这个数则会再后面出现,和现在的树组成逆序数。
例如:
还是拿样例说话
9 1 0 5 4
离散化后为5 2 1 4 3
树插入5之后,树中1~5的数有一个,5-1=4,还有4个本来在5前面的数没有出现,所以ans+=4
插入2后,树中1~2范围的数有1个,ans+=2-1=1
插入1后,1~1范围内的数有1个,ans+=1-1=0
……
以此类推,可以求出逆序数的个数。
AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MEM(a) memset(a,0,sizeof(a))
#define lowbit(x) ((x)&-(x))
using namespace std;
typedef long long ll;
const int maxn= 500000+10;
ll n;
ll tree[maxn]={0};
ll nums[maxn]={0};
ll lnums[maxn]={0};
ll gg[maxn]={0};
ll sum(ll x){
ll sum=0;
while(x>0){
sum+=tree[x];
x-=lowbit(x);
}
return sum;
}
void add(ll x,ll d){
while(x<=n){
tree[x]+=d;
x+=lowbit(x);
}
}
int main()
{
while(scanf("%d",&n)&&n){
MEM(tree);
MEM(gg);
MEM(lnums);
MEM(nums);
for(int i=0;i<n;i++)
{
scanf("%d",nums+i);
lnums[i]=nums[i];
}
sort(lnums,lnums+n);
for(int i=0;i<n;i++){
gg[i]=lower_bound(lnums,lnums+n,nums[i])-lnums+1;
}
//cout<<"ok"<<endl;
ll ans=0;
for(int i=0;i<n;i++){
add(gg[i],1);
ans+=gg[i]-sum(gg[i]);
}
cout<<ans<<endl;
}
return 0;
}