线段树- ZJU Minimum Inversion Number

本文介绍了一个算法问题——寻找一组数字序列经过特定变换后的最小逆序数。通过分析相邻排列间逆序数的变化规律,提出了两种解决方案:一种是时间复杂度为O(n^2)的直接方法;另一种则是结合堆排序和二叉排序树改进后的算法。
 
Minimum Inversion Number

Time limit: 1 Seconds   Memory limit: 32768K  
Total Submit: 1759   Accepted Submit: 855  

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

Sample Output
16
 
 
解答方案
该问题关键在于相邻的两个排列之间逆序序列的个数之间的关系。假设一个排列为ai,ai+1,..aj。下一个排列为ai+1,..aj,ai,容易看出两个排列之间逆序个数之间的变化主要是因为ai位置移动造成的,设comp[i]表示排列中大于ai的数的个数,当ai移动到排列的末尾后,逆序个数增加comp[i]。但是需要注意原来排列中比ai小的数与ai构成逆序,当ai移动到排列的末尾后,原来这种逆序关系不再存在,因此排列的逆序个数需要同时减少n-comp[i],其中n为排列中元素的个数。扫描完所有排列,其中的最小逆序个数即为所求解。
Code:
#include <stdio.h>
#include <string.h>
#define MAXN 5000
inline int minFunc(int ,int );
int main(int argc,char **argv)
{
 int seq[MAXN];
 int comp[MAXN];
 int i,j,n,cur;
 int minValue;
 while(scanf("%d",&n) != EOF)
 {
  memset(comp,0,sizeof(comp));
  minValue = 0;
  for(i=0;i<n;i++)
  {
   scanf("%d",&seq[i]);
   for(j=0;j<i;j++)
   {
    if(seq[j]>seq[i])
    {
     comp[i]++;
     minValue ++;
    }
    else if(seq[j]<seq[i])
    {
     comp[j]++;
    }
   }
  }
  cur = minValue;
  //cindex = n-1;
  //printf("cur = %d/n",cur);
  for(i=0;i<n-1;i++)
  {
   cur = cur+comp[i]-(n-1-comp[i]);
   minValue = minFunc(minValue,cur);
   //printf("comp[%d]=%d/n",i,comp[i]);
   /*
   if(cur<minValue)
   {
    minValue = cur;
    cindex = i;
   }
   */
  }
  printf("%d/n",minValue);
  /*
  for(i=0;i<n;i++)
   printf("%d ",seq[(i+cindex+1)%n]);
  printf("/n");
  */
 }
 return 0;
}
inline int minFunc(int a,int b)
{
 return a<b?a:b;
}
该算法的复杂度为O(n^2),主要是因为在计算序列中下一个元素的comp时候,需要调节前面元素的comp。改进方法是采用二分排序使得计算的复杂度降为O(nlogn),可以采用二叉排序树来实现。
算法改进:
结合堆排序和二叉排序树对算法进行了改进.代码长度增加了不少,但是运行时间降低了很多。主要改变是对整个序列用堆排序求出每个元素在序列中大于它的元素的个数,
用二叉排序树求初始序列中逆序的个数.下面的步骤跟原来的算法一样,采用递推求每个序列中逆序的个数. (正好趁这个机会实现了堆排序,函数接口仿照qsort.二叉排序树求初始序列个数逆序个数的思想请参见另外一个帖子:求序列中逆序的个数)
Code:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXN 5000
inline int minFunc(int ,int );
typedef struct
{
 int num;
 int val;
}inversionElem;
typedef struct
{
 int val;
 int lchild,rchild;
 int lsize,rsize;
 int repeat;
}treeNode;
/*采用二叉排序树*/
int getInversionNum_BinarySortedTree(int [], int );
static bool find(treeNode [],int ,int &,int ,int & );
static int insert(treeNode [],int &, int, int& );
static void heapSort(void * , int , int , int (*)(const void *, const void *) );
static void sift(void * , int , int , int , int (*)(const void *, const void *) , void * );
static int compare(const void *, const void * );
int main(int argc,char **argv)
{
 int seq[MAXN];
 int comp[MAXN];
 inversionElem heap[MAXN];
 int i,j,n,cur,cindex;
 int minValue;
 while(scanf("%d",&n) != EOF)
 {
  minValue = 0;
  for(i=0;i<n;i++)
  {
   scanf("%d",&seq[i]);
   heap[i].val = seq[i];
   heap[i].num = i;
   comp[i] = 0;
  }
  heapSort(heap,n,sizeof(inversionElem),compare);
  /*
  for(i=0;i<n;i++)
   printf("%d ",heap[i].val);
  printf("/n");
  for(i=0;i<n;i++)
   printf("%d ",heap[i].num);
  printf("/n");
  */
  int pre = -1 ;
  for(i=0;i<n;i++)
  {
   if(i>0&&heap[i-1].val==heap[i].val)
    j = pre;
   else
    j = pre +1;
   pre = j;
   comp[heap[i].num] = j;
  }
  minValue = getInversionNum_BinarySortedTree(seq,n);
    
  /*
  for(i=0;i<n;i++)
  {
   scanf("%d",&temp);
   int low = 0, hig = i-1;
   while(low<=hig)
   {
    int mid = (low+hig)>>1;
    if(heap[mid]<=temp)
     low = mid + 1;
    else
     hig = mid -1 ;
   }
   for(j=hig+1;j<i;j++)
   {
    heap[j+1] = heap[j];
    order[j+1] = order[j];
   }
   heap[hig+1] = temp;
   order[hig+1] = i;
   comp[i] = i - 1 - hig;
   minValue +=comp[i];
   for(j=hig;j>=0;j--)
    if(heap[j]!=temp)
     comp[order[j]]++;
  }
  */
  /*
  for(i=0;i<n;i++)
   printf("comp[%d]=%d/n",i,comp[i]);
  */
  cur = minValue;
  cindex = n-1;
  //printf("cur = %d/n",cur);
  for(i=0;i<n-1;i++)
  {
   cur = cur+comp[i]-(n-1-comp[i]);
   //printf("comp[%d]=%d/n",i,comp[i]);
   if(cur<minValue)
   {
    minValue = cur;
    cindex = i;
   }
   //minValue = minFunc(minValue,cur);
   //printf("cur = %d/n",cur);
  }
  printf("%d/n",minValue);
  /*
  for(i=0;i<n;i++)
   printf("%d ",seq[(i+cindex+1)%n]);
  printf("/n");
  */
 }
 return 0;
}
static void heapSort(void * arr, int nElem, int nsize, int (*f)(const void *, const void *))
{
 int i;
 void *temp = malloc(nsize);
 for(i=nElem>>1;i>0;i--)
  sift(arr,i-1,nElem-1,nsize,f,temp/* 重复利用动态分配内存*/); //arr[i-1..nElem-1]
    
 for(i=nElem-1;i>=1;i--)
 {
  memcpy(temp,((char *)arr)+i*nsize,nsize);
  memcpy(((char *)arr)+i*nsize,((char *)arr),nsize);
  memcpy(((char *)arr),temp,nsize);
  sift(arr,0,i-1,nsize,f,temp);
 }
 free(temp);
}
static void sift(void *arr,int left, int right, int nsize,int (*f)(const void *,const void *),void *temp)
{
 /*
  * arr[left+1..right]已经满足堆的性质,
  * 加入arr[left]使得arr[left..right]满足堆的性质
  */
 int i = left;
 int j = (i<<1)+1;
 bool finished = false;
 memcpy(temp,((char *)arr)+left*nsize,nsize);
 while(j<=right&&!finished)
 {
  if(j<right&&f((void *)(((char *)arr)+j*nsize),(void *)(((char *)arr)+(j+1)*nsize))>0)
   j++;
  if(f(temp,(void *)(((char *)arr)+j*nsize))<=0)
   finished = true;
  else
  {
   memcpy(((char *)arr)+i*nsize,((char *)arr)+j*nsize,nsize);
   i = j;
   j<<=1;
   j++;
  }
 }
 memcpy(((char *)arr)+i*nsize,temp,nsize);
}
static int compare(const void *a, const void *b)
{
 return ((inversionElem *)a)->val-((inversionElem *)b)->val;
}
/*采用二叉排序树*/
int getInversionNum_BinarySortedTree(int a[], int n)
{
 int root,inversionNum,tlen;
 treeNode T[MAXN];
 root = tlen = -1;
 inversionNum = 0;
 for(int i= 0;i < n;i++)
 {
  inversionNum += insert(T,root,a[i],tlen);
 }
 return inversionNum;
}
static bool find(treeNode T[],int root,int &pre,int val,int &r)
{
 bool found;
 if(root==-1)
 {
  found = false;
 }
 else
 {
  pre = root;
  if(T[root].val>val)
  {
   T[root].lsize++;
   r+=T[root].repeat;
   r+=T[root].rsize;
   found = find(T,T[root].lchild,pre,val,r);
  }else if(T[root].val<val)
  {
   T[root].rsize++;
   found = find(T,T[root].rchild,pre,val,r);
  }else
  {
   found = true;
   T[root].repeat++;
  }
 }
 return found;
}
static int insert(treeNode T[],int &root,int val, int &tlen)
{
 int inversionNum = 0;
 int pre;
 
 bool found = find(T,root,pre,val,inversionNum);
 if(!found)
 {
  tlen ++;
  T[tlen].val = val;
  T[tlen].lchild = T[tlen].rchild = -1;
  T[tlen].lsize = T[tlen].rsize = 0;
  T[tlen].repeat = 1;
  if(root==-1)
  {
   root = 0;
  }else
  {
   if(val<T[pre].val)
   {
    T[pre].lchild = tlen;
   }else
   {
    T[pre].rchild = tlen;
   }
  }
 }
 return inversionNum;
}
/*采用二叉排序树*/
/*
inline int minFunc(int a,int b)
{
 return a<b?a:b;
}
*/
当然最好的方法是利用线段树来求解,现在正在寻求这个解法。
 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值