Minimum Inversion Number
题目链接: HDU - 1394求逆序数的方法有多种, 一可以直接暴力求, O(n^2)的复杂度, 二可以用归并排序求, O(nlogn)的复杂度,
现在学了线段树又可以用线段树求, O(nlogn)的复杂度;
前两种方法就不多说了, 就只谈用线段树求逆序数的方法;
9 2 8 3 6 7 5 这个序列的逆序数是12,是怎么出来的呢?
先看9, 前边没有比他大的数, 贡献0,
2, 前边有一个比它大的数, 贡献1,
8, 贡献1;3贡献2;6贡献2;7贡献2;5贡献4,合计12;
由此可看出只需要找出写下每个数之前有几个比他大的数已写即可;
我们可以先建个空的线段树, 节点为1~n, 每次写下一个数之前查询该数所在位置后边有几个数被写, 然后再记录该数;
这个题要求求出所给序列中按不同排列次序最小的逆序数是多少, 如2 8 7 4 3 的不同排列次序可以是8 7 4 3 2 或7 4 3 2 8。。。。。。
若一个序列中是0~n-1时:
当一个数i在序列首时, 序列中比他小的数只有i个, 当他移到序列尾时, 前边比他大得数有n-i-1个;
所以每次转移逆序数变化为sum=sum-i+n-1-i;
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5010;
int n;
int a[maxn];
struct node{
int left, right, x;
}tree[maxn<<2];
void build(int m, int l, int r){
tree[m].left=l;
tree[m].right=r;
if(l==r){
tree[m].x=0;
return;
}
int mid=(l+r)>>1;
build(m<<1, l, mid);
build(m<<1|1, mid+1, r);
tree[m].x=tree[m<<1].x+tree[m<<1|1].x;
}
void update(int m, int a){
if(tree[m].left==a&&tree[m].right==a){
tree[m].x++;
return;
}
int mid=(tree[m].left+tree[m].right)>>1;
if(a<=mid) update(m<<1, a);
else update(m<<1|1, a);
tree[m].x=tree[m<<1].x+tree[m<<1|1].x;
}
int query(int m, int l, int r){
if(tree[m].left==l&&tree[m].right==r){
return tree[m].x;
}
int sum1=0, sum2=0;
int mid=(tree[m].left+tree[m].right)>>1;
if(r<=mid) return query(m<<1, l, r);
else if(l>mid) return query(m<<1|1, l, r);
return query(m<<1, l, mid)+query(m<<1|1, mid+1, r);
}
int cnt[maxn], sum=0;
int main(){
while(~scanf("%d", &n)){
build(1, 1, n);
sum=0;
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
sum+=query(1, a[i]+1, n);//先查询再更新;因为输入值为0~n-1, 节点值为1~n, 所以向后错一个位置;
update(1, a[i]+1);
}
int ans=sum;
for(int i=1; i<=n; i++){
//printf("i:%d sum:%d\n", i, sum);
sum=sum-a[i]+n-a[i]-1;
ans=min(ans, sum);
}
printf("%d\n", ans);
}
return 0;
}