分治:分而治之,就是将一个规模比较大的问题分解为相同的规模较小的子问题,通过解决子问题来解决该问题。
古时候,信息、交通不便利。比如朝廷要征收全国老百姓的粮食,怎么征收呢。皇帝就把征收粮食的任务交给户部,让户部去征收粮食,户部又将征收粮食的任务委任给了各个州,各个州又将征收粮食的任务交给了各个县,各个县又将征收粮食的任务交给各个村,各个村的村民将粮食交给自己所属的县,各个县将征收的粮食交给自己所属的州,各个州又将粮食上交到朝廷,便完成征收粮食。
分治就是将一个大的问题,一步一步向下扩展为子问题,,即不能再分解或者已经可以很好解决该问题,然后通过解决该子问题的得到的结果一步一步向上合并,最终解决该问题。
我们来分析一下归并排序,将一个无序的序列通过排序使其成为有序序列。怎么分治呢,分治的思想就两点,一是将问题分解为可以解决的子问题,二是将子问题的结果合并。那么我们先将整个序列分解为子序列,什么时候可以解决呢,就是每个子序列只含一个数的时候,该序列就是有序的了,然后我们就开始合并子问题的结果,一步一步向上合并,最终该序列变得有序了。
tips:归并排序可以用来统计逆序对数,
逆序对数:对于每一个数,如果该数大于后面的某个数是就叫逆序对 比如 3 2 1 逆序对数为3,对于3来说有(3,2)和(3,1),对于2来说有(2,1),对于1 没有逆序对,所以逆序对数一共有3对。
那么归并排序是如何统计逆序对数的呢,考虑这样一个问题,我们分解完序列后,在合并某两个子序列时,该子序列是有序的,在合并的时候每次将两个子序列中较小的那个数先放进合并的那个序列里,如果前一个的子序列(即在给定的序列中,该序列的下标靠前)里面的数大于后面的一个子序列(即在给定的序列中,该序列的下标靠后),就将后面一个子序列的元素先放到要合并到的那个序列里。因为两个子序列都是有序的,所以在统计的时候,前面的那个子序列的某个数大于后面子序列的某个数时,前面的那个子序列那个数后面的每一个数都要大于后面那个子序列里面的那个数,所以统计时要加上前面那个序列的那个数后面的每一个数。
POJ 2299 Ultra_Qsuickort
题目大意:就是先给你一个整数n,然后给你含n个数的序列,让你计算逆序对数,由于给的数据比较多,所以不能逐个枚举,故采用分治来解决。下面给出代码
#include<cstdio>
#include<iostream>
#include<cstdlib>
using namespace std;
const int maxn=500000+7;
int a[maxn];
long long int count;
void merge(int* a,int l,int mid,int r) //归并
{
int i,j,k;
int* temp=(int*)malloc((r-l+3)*sizeof(int)); //runtime error 动态开辟的空间不足
i=l,j=mid+1;k=0; //一个技巧我们可以多开辟几个空间,来避免指针越界
while(i<=mid&&j<=r){
if(a[i]<=a[j]){
temp[k++]=a[i++];
}
else {
temp[k++]=a[j++];
count+=mid-i+1; //统计逆序对数 如果左边第i个数大于右边某个数x 由于数组是有序的
} //那么i到mid中间的数也会大于有边的x 所以加上mid-i+1
}
while(i<=mid)temp[k++]=a[i++];
while(j<=r)temp[k++]=a[j++];
for(i=l,k=0;i<=r;i++,k++)
a[i]=temp[k];
free(temp);
}
void merge_sort(int* a,int l,int r) //将问题分解为子问题
{
if(l<r){ //切记不可写为while
int mid;
mid=(l+r)>>1;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
merge(a,l,mid,r); //解决子问题
}
}
int main()
{
int n;
while(scanf("%d",&n)==1&&n){
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
count=0;
merge_sort(a,0,n-1);
printf("%lld\n",count);
}
return 0;
}