http://acm.hdu.edu.cn/showproblem.php?pid=1394
先说一下逆序数的概念:
在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那末它们就称为一个逆序。
一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。
如2431中,21,43,41,31是逆序,逆序数是4,为偶排列。
——这是北大《高等代数》上的定义。
题意是说给出一个序列,前m个可以后移,每次都产生不同逆序数,问:最小逆序数。
思路:先求出原始的逆序数,然后再枚举每个位置,新逆序数=原来的-比当前数小的+比当前数大的,即sum = sum - a[i] + (n - a[i] - 1);(ps:为啥这样子呢?举个例子有原始序列:0 3 1 2,把0往后移有3 1 2 0,那么移动后的逆序数=原来的 - 原来与0构成逆序的[因为以0作为标准来看,这部分已不能构成逆序了] + 移动后比0大的[新形成这部分能够成逆序])输出min。
注意用树状数组求原始逆序数时要从后往前扫!这样才能找出前面比a[i]大的数的个数。
树状数组版
int n;
int a[5005], c[5005];
void update (int i, int num)
{
while (i <= n)
{
c[i] += num;
i +=i&(-i) ;
}
}
int Sum (int i)
{
int sum = 0;
while (i > 0)
{
sum += c[i];
i -= i&(-i);
}
return sum;
}
int min(int a,int b)
{
return a>b?b:a;
}
int main ()
{
while (scanf ("%d", &n) != EOF)
{
int i;
memset (c, 0, sizeof (c));
int sum = 0;
for (i = 1; i <= n; i ++)
{
scanf ("%d", &a[i]);
}
for (i = n; i >=1; i --)//最初的逆序数,要从后往前扫!!!
{
sum += Sum (a[i]);
update (a[i] + 1, 1);
}
int minn = sum;
for (i = 1; i <= n; i ++)
{
sum = sum - a[i] + (n - a[i] - 1);
minn = min (sum, minn);
}
printf ("%d/n", minn);
}
return 0;
}
归并排序版
#define N 5005
int tmp[N];
int a[N];
int b[N];
int num;
void merge_sort1(int l,int mid,int r){
int i = l,j = mid+1,k;
k = 1;
while(i<=mid && j<=r){
if(a[j]<a[i]){
tmp[k++] = a[j++];
num += mid-i+1;//核心的一行,计数逆序数对
} else tmp[k++] = a[i++];
}
while(i<=mid)tmp[k++] = a[i++];
while(j<=r)tmp[k++] = a[j++];
for(i=l,k=1; i<=r; i++,k++){
a[i] = tmp[k];
}
}
void merge_sort(int s,int t){
if(s==t)return ;
int mid = (s+t)>>1;
merge_sort(s,mid);
merge_sort(mid+1,t);
merge_sort1(s,mid,t);
}
int main(){
int n;
while(scanf("%d",&n) != -1){
int i,j;
for(i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i] = a[i];
}
num = 0;
merge_sort(1,n);
int ans = num;
for(i=1;i<=n;i++){
num = num-b[i]+(n-1-b[i]);
ans = min(ans,num);
}
printf("%d\n",ans);
}
return 0;
}