hdu(1394)——Minimum Inversion Number
发布日期:2021-09-19 06:09:11 浏览次数:0 分类:技术文章

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
  
  
101 3 6 9 0 8 5 7 4 2
 

Sample Output
  
  
16

我后来想了下,为什么这道题要用线段树呢?原来线段树就是一个辅助作用,他能帮助我们更快地找出区间和,而不是说它能够帮我们完全解决问题。

这道题的大致意思是:给你一个逆序数,然后每次都可以把第一个数可以移到最后一个位置去,然后问你这n-1种序列每一种的逆序数是多少,然后问你所有这几种序列中逆序数的最小值是多少。

这道题好像有四种做法,然后现在我只了解了2种,其余的以后再更新。

1:首先第一种想法是找规律,我们先求出最初的那个序列的逆序数的个数,记为sum,然后每次把第一个移动到最后一个就会使sum减少a[i],但是又会增加n-(a[i]+1)个。这个规律如果不理解的话可以举几组例子来想想看。然后for个n遍,就可以找到最小的逆序数了。

#include<stdio.h>#include<string.h>#include<algorithm>using namespace std;#define maxn 5555#define inf 99999999int a[maxn];int main(){    int n;    while(~scanf("%d",&n)){        int min1=inf,num=0;        for(int i=0;i<n;i++) scanf("%d",&a[i]);        for(int i=0;i<n;i++){            for(int j=i+1;j<n;j++){                if(a[i]>a[j]&&i<j) num++;            }        }        if(min1>num) min1=num;        //下面是找规律得出的;        //就是每一次把最前面的移到最后,逆序数对数会减少a[i]个,但是会增加n-(a[i]+1)个,举几个例子就知道了         for(int i=0;i<n;i++){            num=num-a[i]+n-(a[i]+1);            if(min1>num) min1=num;        }        printf("%d\n",min1);    }}/*41 3 2 0*/

第二种想法是线段树,

这个说实话一些题解想法写的不够详细,然后我看了几天才是真正的理解。

这里给大家推荐一下这个人写的博客:http://blog.sina.com.cn/s/blog_691ce2b70101ldmm.html

可以去看一下他的思路,但是不一定要学习他的线段树的写法。

思路就是:我们对按照顺序读入的n个数然后每次读入一个数的时候就看一下大于它的且已经出现过的数的个数。

比如说是:有10个数,然后我们之前已经读入了3,6,7,然后输入0的时候就去1~10的范围去询问那些数已经出现过了,那么那些数就是它的逆序数。那么在这个例子中是3,6,7,所以它的逆序数是3个。

#include<stdio.h>#include<string.h>#include<iostream>#include<algorithm>using namespace std;#define maxn 5555#define inf 99999999int a[maxn];struct node{	int l,r,sum;}tree[maxn*4];int ans=0;void pushup(int v){	int temp=v*2;	tree[v].sum=tree[temp].sum+tree[temp+1].sum;}void build(int l,int r,int v){	tree[v].l=l;	tree[v].r=r;	tree[v].sum=0;	if(l==r) return;	int temp=v*2;	int mid=(l+r)/2;	build(l,mid,temp);	build(mid+1,r,temp+1);}int query(int l,int r,int v){	if(l==tree[v].l&&r==tree[v].r){		return tree[v].sum;	}	int temp=v*2;	int mid=(tree[v].l+tree[v].r)/2;	if(r<=mid) return query(l,r,temp);	else if(l>mid) return query(l,r,temp+1);	else{		return query(l,mid,temp)+query(mid+1,r,temp+1);	}}void update(int pos,int v){	if(tree[v].l==tree[v].r){		tree[v].sum++;		return ;	}	int mid=(tree[v].l+tree[v].r)/2;	int temp=v*2;	if(pos<=mid) update(pos,temp);	else update(pos,temp+1);	pushup(v);		//记得这里要pushup!!! }int main(){	int n;	while(~scanf("%d",&n)){		memset(a,0,sizeof(a));		for(int i=0;i<n;i++) scanf("%d",&a[i]);		build(0,n-1,1);		int sum=0,min1=inf;		for(int i=0;i<n;i++){			ans=query(a[i],n-1,1);			sum+=ans;			update(a[i],1);		}		for(int i=0;i<n;i++){			sum+=n-(a[i]+1)-a[i];			if(sum<min1) min1=sum;		}		printf("%d\n",min1);	}}/*101 3 6 9 0 8 5 7 4 241 3 2 0*/
其实下面那部分的还是和第一种方法是一样的,所以线段树维护的还是求出sum的过程(即为原先数列的逆序数的个数)。

多积累,多AC,加油!!

上一篇:Codeforces Round #306 (Div. 2)
下一篇:poj(2676)——Sudoku