/*Minimum Inversion Number Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 11841 Accepted Submission(s): 7259 Problem Description The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj. For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following: a1, a2, ..., an-1, an (where m = 0 - the initial seqence) a2, a3, ..., an, a1 (where m = 1) a3, a4, ..., an, a1, a2 (where m = 2) ... an, a1, a2, ..., an-1 (where m = n-1) You are asked to write a program to find the minimum inversion number out of the above sequences. Input The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1. Output For each case, output the minimum inversion number on a single line. Sample Input 10 1 3 6 9 0 8 5 7 4 2 Sample Output 16 Author CHEN, Gaoli Source ZOJ Monthly, January 2003 Recommend Ignatius.L */ #include<stdio.h> int segtree[20010], fa[20010], a[5010]; void build(int i, int x, int y) { segtree[i] = 0;//初始化个数为0 if(x == y) { fa[x] = i;//记录原始值所在二叉树的位置 return ; } int j = (x+y)>>1; build(i<<1, x, j); build(i<<1|1, j + 1, y); } void update(int i)//从叶子开始往上更新 { if(i == 1)//更新到根节点结束 return; int j = i>>1;//当前节点的父节点 segtree[j] = segtree[j<<1] + segtree[j<<1|1]; //更新父节点的值 update(j);//递归更新 } int query(int i, int l, int r, int L, int R)//返回l到r中以出现的个数 { if(l <= L && R <= r)//如果查询区间包含当前区间,返回该区间的值 return segtree[i]; int num = 0, j = (L+R)>>1; if(l <= j)//否则继续缩小当前区间 num += query(i<<1, l, r, L, j); if(r > j) num += query(i<<1|1, l, r, j+1, R); return num;//最终返回查询区间的值 } int main() { int i, j, k, n, m; while(scanf("%d", &n) != EOF) { m = 0; build(1, 0, n-1);//注意这里是从0开始的~~ for(i = 0; i < n; i++) { scanf("%d", &a[i]); /*必须先查询后更新 */ /*我这里是将线段树中输入值的位置标记为1*/ /*如果先更新后查询每次会多加一个本身的个数*/ /*其次查询最好是查询后半段,而不是查询前半段再拿a【i】去减去查询的值 */ m += query(1, a[i], n-1, 0, n-1);//从根区间开始查询当前值之前已出现的数字个数 segtree[fa[a[i]]] = 1;//二叉树对应数字的位置标为1 update(fa[a[i]]); //向上更新二叉树的区间和 } int min = m; /* 因为序列为[0, n-1],若最前面一个数为x,序列中比x 小的数为[0, x-1], 共x个,比x大的数为[x+1, n-1], 共n-x-1个,将x移到最后,比x小的数的逆序数均减1, x的前面比x大的数有n-x-1个,x的逆序数增加n-x-1。 所以新序列的逆序数为原序列的逆序数加上n-2*x-1。 */ for(i = 0; i < n; i ++) { m = m - a[i]*2 + n-1; if( min > m ) min = m; } printf("%d\n", min); } return 0; }
题意:给定一个序列,求序列的逆序数对数,其次该序列的前n位可以移到最后产生一个新的序列,总共有n种序列,求这n个序列中逆序数对数最少的个数。
思路:这题有两个关键,第一是想到原输入序列的逆序数对数和用线段树去求出来,其思路就是对于每一个输入的数都标记进入二叉树,其次二叉树区间m~n的意思就是在序列m~n中已经出现了几个数,然后每次输入一个数就可查询“输入的数到n-1“之间的数已经出现了几个,这个值就是该输入的数所对应的逆序数的对数。
第二个关键就是,求除了输入的原序列以外其余n-1种序列的逆序数对数的和,这个我就不说了上面注释有。
体会:要学会活用线段树是个难点。