HDU 1394 Minimum Inversion Number【线段树求逆序数】http://acm.hdu.edu.cn/showproblem.php?pid=1394
Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 11014 Accepted Submission(s): 6772
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.
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
【分析】主要是利用线段树求逆序数,建的是一棵空树,然后每插入一个点之前,统计大于这个数的有多少个,直到所有的数都插入完成,就结束了逆序树的统计。
要得出答案主要是利用了一个结论,如果是0到n的排列,那么如果把第一个数放到最后,对于这个数列,逆序数是减少y[i],而增加n-1-y[i]的。(可以这样想,因为是第一个数,所有的数都在它后面,那么在当前位置pos比它大的数也在它后面,那么第一个数调到后面之后,在pos不成立的逆序数就成立了,所以多了n-y[i]-1,但是也少了在pos成立的逆序数,即y[i]个)
【归并排序求逆序数代码】//Accepted 1394 15MS 316K 1441B C++
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5000 + 20;
long long n;
long long a[maxn], c[maxn], num[maxn], cnt;
void mergeSort(int l, int r)//求a[]序列的逆序数cnt模板:mergeSort(0, n);
{
long long mid, i, j, tmp;
if(r >l+1){
mid = (l+r)/2;
mergeSort(l, mid);
mergeSort(mid, r);
tmp = l;
for(i=l, j=mid; i<mid && j<r; ){
if(a[i]>a[j]){
c[tmp++] = a[j++];
cnt += mid-i;//总的逆序数
}
else c[tmp++] = a[i++];
}
if(j<r) for(; j<r; j++) c[tmp++] = a[j];
else for(; i<mid; i++) c[tmp++] = a[i];
for(i=l; i<r; i++) a[i] = c[i];
}
}
int main()
{
while(~scanf("%d", &n))
{
long long mmin = 0xfffffff;
for(int i=0; i<n; i++){
scanf("%d", &a[i]);
num[i] = a[i];
}
cnt = 0;
mergeSort(0, n);
mmin = min(cnt, mmin);
long long t = cnt;
for(int i=0; i<n; i++)
{
// int j;
// for(j=i; j<n; j++) a[j-i] = num[j];
// for(int k=j-i, cc=0; k<n; k++, cc++) a[k] = num[cc];
// cnt = 0;
// mergeSort(0, n);
/*
求最小逆序数的时候有个巧妙的想法,
当把x放入数组的后面,此时的逆序数
应该为x没放入最后面之前的逆序总数
加上(n-x)再减去(x-1);
sum = sum+(n-x[i])-(x[i]-1)。
*/
t = t-num[i]+n-num[i]-1;
mmin = min(t, mmin);
}
printf("%d\n", mmin);
}
return 0;
}
【线段树求逆序数代码】
//线段树求逆序数
//Accepted 1394 31MS 316K 2032 B C++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 5000+10;
struct NODE
{
int w;
int fmax, fmin, rmax, rmin;
//分别代表序列的前方比w大的个数,小的个数,
//后方比w大的个数,小的个数
}node[maxn];
int tree[maxn*4], ffmax, ffmin;
void build(int l, int r, int k)
{
int m = (l+r)/2;
tree[k] = 0;
if(l==r) return ;
build(l, m, k*2);
build(m+1, r, k*2+1);
}
void updata(int l, int r, int k, int w)
{
int m = (l+r)/2;
tree[k]++;
if(l==r) return ;
if(w <= m){//往左走
ffmax += tree[k*2+1];//记录比w大的个数
updata(l, m, k*2, w);
}
else{//往右走
ffmin += tree[k*2];//记录比w小的个数
updata(m+1, r, k*2+1, w);
}
}
int main()
{
int n, mmin, mm;
while(~scanf("%d", &n))
{
build(1, n, 1);
mm = 0;
for(int i=1; i<=n; i++)
{
scanf("%d", &node[i].w);
node[i].w++;//变成1~n
ffmax = 0; ffmin = 0;
updata(1, n, 1, node[i].w);
node[i].fmax = ffmax;//前面输入的比w大的个数
node[i].fmin = ffmin;//前面输入的比w小的个数
node[i].rmax = (n-node[i].w)-ffmax;//根据前面的可推出后面比w大的个数
node[i].rmin = (node[i].w-1)-ffmin;//根据前面的可推出后面比w小的个数
mm += ffmax;//记录输入序列的总逆序数
}
mmin = mm;//记录n个(循环)序列的最小的总逆序数
for(int i=1; i<=n; i++)//把第i个数放到序列的最后
{
mm = mm-node[i].rmin;//移动后,对后方小于w(i)的逆序数的影响
mm = mm-node[i].fmin;//移动后,对己经循环的前方数(小于w(i))的逆序数的影响
mm = mm+node[i].fmax;//移动后,对本身(小于己经循环的前方数的逆序数)的影响
mm = mm+node[i].rmax;//移动后,对本身(小于还没循环数的逆序数)的影响
mmin = min(mmin, mm);
}
printf("%d\n", mmin);
}
return 0;
}