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; }