Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 7917 Accepted Submission(s): 4857
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.
题意:
给出N(0到5000)个数,这N个数属于0到N-1的其中一个数,任意输入这N个数的其中一个序列。求这个序列的逆序数,求完这个排列的逆序数后,将第一位后移到最后一位,故产生一共N种排列,求出这N种排列的最小逆序数。
思路:
乍一看完全没头绪要怎么做,可是进一步分析,可以得出一条关系式:
设当前排列的逆序数为sum,第一个数所产生的的逆序数对为first,那么他所产生的逆序数必定为first。因为它是第一位数,并且这个序列是没有相同项的,如果第一个数为6的话,则在它后面的N-1位各不相同,并且由0到N除了6的数组成,那么后面必有0,1,2,3,4,5一共6个数小于它,并且有N-1-6大于6。所以当第一个数后移后,新的序列的逆序数为sum=sum-(first)+(N-1-first)。
first是本身的值,所以已知,N是总数,也已知,那么关键就是算sum,就是一开始这个序列的逆序数sum。
有两种方法:
1.循环两次,每次以i为起点开始,用j来对i+1后面的数进行扫描,如果num[i]>num[j]则sum++,这样的话就是从后面的数进行判断;
2.如果非得用线段树做的话,就要换一种思考方式做,从前面的数开始判断。在结构体中开一个c域,用来记录这个区域内有几个数,在插入一个数num[i]之前,先判断[num[i],N-1]这个范围内有几个数,用sum+=c来记录,循环判断所有的数后,sum即为这个序列的逆序数。
比如这个样例1 3 6 9 0 8 5 7 4 2;那么插入1之前先判断1的前面有多少个大于1的数(sum+=0),然后到插入3的时候前面有多少个大于3的数(sum+=0),当到0的时候有4个大于0的数,则sum=4,直到最后一个数2,2的前面有7个大于2的数,所以sum+=7,最后得出来sum也是22。
AC:
Method 1:
#include<stdio.h>
int main()
{
int num[5000+5];
int n;
while(scanf("%d",&n)!=EOF)
{
int sum=0;
int min;
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
for(int i=1;i<=n-1;i++)
for(int j=i+1;j<=n;j++)
if(num[i]>num[j]) sum++;
min=sum;
for(int i=1;i<=n-1;i++)
{
sum=sum-2*num[i]-1+n;
min=min>sum?sum:min;
}
printf("%d\n",min);
}
return 0;
}
Method 2:
#include<stdio.h>
#define MAX 5000+5
typedef struct
{
int l;
int r;
int c;
}node;
node no[MAX*3];
int num[MAX],fir[MAX];
int sum;
void build(int from,int to,int i)
{
int mid=(from+to)/2;
no[i].l=from;
no[i].r=to;
if(from==to)
{
no[i].c=0;
return;
}
build(from,mid,2*i);
build(mid+1,to,2*i+1);
no[i].c=no[2*i].c+no[2*i+1].c;
}
void add(int a,int i)
{
int mid=(no[i].l+no[i].r)/2;
if(no[i].l==no[i].r&&no[i].l==a)
{
no[i].c++;
return;
}
if(a<=mid) add(a,2*i);
if(a>=mid+1) add(a,2*i+1);
no[i].c=no[2*i].c+no[2*i+1].c;
}
void find(int from,int to,int i)
{
int mid=(no[i].l+no[i].r)/2;
if(no[i].l==from&&no[i].r==to)
{
sum+=no[i].c;
return;
}
if(from>=mid+1) find(from,to,2*i+1);
if(to<=mid) find(from,to,2*i);
if(from<=mid&&to>=mid+1)
{
find(from,mid,2*i);
find(mid+1,to,2*i+1);
}
}
int main()
{
int n,min;
while(scanf("%d",&n)!=EOF)
{
min=0;
sum=0;
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
build(0,n-1,1);
for(int i=1;i<=n;i++)
{
find(num[i],n-1,1);
add(num[i],1);
//插入前先判断
}
min=sum;
for(int i=1;i<=n-1;i++)
{
sum=sum-2*num[i]-1+n;
min=min>sum?sum:min;
}
printf("%d\n",min);
}
return 0;
}
总结:
1.题目说是”The input consists of a number of test cases.“所以也应该用EOF输入;
2.不要害怕题目本质,进一步分析就能得出答案。
本文介绍了一种算法问题——最小逆序数问题,探讨了如何通过两种不同的方法找到一系列数字排列中的最小逆序数,包括直接计算逆序数和利用线段树优化计算过程。

被折叠的 条评论
为什么被折叠?



