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.不要害怕题目本质,进一步分析就能得出答案。