hdu 1394 Minimum Inversion Number
题意描述:给你一个有0--n-1数字组成的序列,然后进行这样的操作,每次将最前面一个元素放到最后面去会得到一个序列,那么这样就形成了n个序列,那么每个序列都有一个逆序数,找出其中最小的一个输出!
这题用暴力可以过,时间是125ms,最好的方法是用线段树,大概是50+ms
不过思想是一样的,首先统计初始的逆序数,因为题目总是将第一个数已到最后,所以原来比它小的数就不是逆序了,而原来比它大成为了逆序,显然在序列
0,1,2,3....n-1中
比a[i]小的数的个数为a[i]
比a[i]大的数的个数为n-a[i]+1
每次移动之后的逆序数sum就变为了
sum=sum-a[i]+n-a[i]-1;
把a[1],a[2]...a[n]都移动一遍,同时记录最小的逆序数,就行了。
暴力代码如下:
#include<stdio.h> int a[5005]; int main() { int n,sum,i,j,ans; while(scanf("%d",&n)!=EOF) { sum=0; for(i=1;i<=n;i++) { scanf("%d",&a[i]); for(j=1;j<i;j++)//统计初始的逆序数和 if(a[j]>a[i]) sum++; } ans=sum; for(i=1;i<=n;i++)//将所有的数都移动一遍,同时记录最小的逆序数 { sum=sum-a[i]+(n-a[i]-1); if(ans>sum) ans=sum; } printf("%d\n",ans); } return 0; }
下面介绍线段树的思想,这题中线段树用来统计初始的逆序数,其余的和暴力的差不多
先建一个空树,每输入一个数,就查询比它大的数的数目,然后更新树的value值(value是用来记录数的,每输入一个数,只要这个线段包含了这个数,value就加1),例如,序列3,2,4,1,5.逆序数,先输入3,查询得到sum=0,并让包含3的线段value都加1(也就是update(3,1)),再输入2时,查询2~5,之前只输入了3,sum=1,,之后更新update(2,1);
下面是线段树代码:
#include<stdio.h> struct node{ int left,right; int value;//记录插入数的个数 }a[5001*3]; int b[5001],sum=0; void build(int s,int t,int step)//在s~t线段建树 { a[step].left=s; a[step].right=t; a[step].value=0; if(a[step].left==a[step].right) return ; int mid=(s+t)/2; build(s,mid,step*2); build(mid+1,t,2*step+1); } void query(int k,int t,int step)//在k~t查找 { if(a[step].left==k&&a[step].right==t) { sum+=a[step].value; return ; } int mid=(a[step].left+a[step].right)/2; if(mid>=t) query(k,t,2*step); else if(mid<k) query(k,t,2*step+1); else { query(k,mid,2*step); query(mid+1,t,2*step+1); } } void update(int k,int step) { a[step].value++; if(a[step].left==a[step].right) return ; int mid=(a[step].left+a[step].right)/2; if(mid>=k) update(k,2*step); else update(k,2*step+1); } int main() { int n,i,ans; while(scanf("%d",&n)!=EOF) { sum=0; build(0,n-1,1); for(i=1;i<=n;i++) { scanf("%d",&b[i]); query(b[i],n-1,1); update(b[i],1); } ans=sum; for(i=1;i<=n;i++) { sum=sum-b[i]+(n-b[i]-1); if(ans>sum) ans=sum; } printf("%d\n",ans); } return 0; }