poj 2299/2188 求逆序数个数(分治or线段树\树状数组)

题意:给定n个数,问通过交换相邻元素的方法将其排序最少需要交换多少个相邻元素。

思路:1、本质上就是求序列的逆序数个数。首先想到的方法当然是基于分治的归并排序外加统计逆序。

2、树状数组做法,先对数组排序。然后扫原数组,每个数在排序好的数组中查找下标,在它前面比它大的个数就是当前元素的逆序对,然后将其删除。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 500005
int s[N],t[N],n;
__int64 merge_sort(int a,int b){
	int i,j,c;
	__int64 res=0;
	if(a == b)
		return 0;
	c = ((a+b)>>1)+1;
	res += merge_sort(a,c-1);//分别统计两段数组内部的逆序个数
	res += merge_sort(c,b);
	for(i = a;i<=b;i++)
		t[i] = s[i];
	for(i = a,j = c;i<c&&j<=b;){//归并排序的过程,外加统计两段数组之间的逆序数
		if(t[i] <= t[j])
			s[i+j-c] = t[i++];
		else{
			s[i+j-c] = t[j++];
			res += c-i;
		}
	}
	while(i<c)
		s[i+j-c] = t[i++];
	while(j<=b)
		s[i+j-c] = t[j++];
	return res;
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d",&n) && n){
		int i;
		for(i = 0;i<n;i++)
			scanf("%d",&s[i]);
		printf("%I64d\n",merge_sort(0,n-1));
	}
	return 0;
}


树状数组:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define min(a,b) ((a)<(b)?(a):(b))
#define N 500005
using namespace std;
int s[N],t[N],tree[N];
int n;
int lowbit(int x){
    return x&(-x);
}
void add(int i,int x){
    int j;
    for(j = i;j<=n;j+=lowbit(j))
        tree[j] += x;
}
int sum(int x){
    int i,res=0;
    for(i = x;i>0;i-=lowbit(i))
        res += tree[i];
    return res;
}
int find(int x){
    int low,high,mid;
    low = 1;
    high = n;
    while(low <= high){
        mid = (low+high)/2;
        if(x == t[mid])
            return mid;
        else if(x < t[mid])
            high = mid-1;
        else
            low = mid+1;
    }
    return 0;
}
int main(){
    while(scanf("%d",&n) && n){
        int i,j;
        long long res = 0;
        memset(tree,0,sizeof(tree));
        for(i = 1;i<=n;i++)//初始化树状数组
            add(i,1);
        for(i = 1;i<=n;i++){
            scanf("%d",&s[i]);
            t[i] = s[i];
        }
        sort(t+1,t+n+1);
        for(i = 1;i<=n;i++){//每次在排好序的数组里查找下标,在它前面比它大的数量就是所求
            j = find(s[i]);
            res += sum(j-1);
            add(j,-1);
        }
        printf("%lld\n",res);
    }
}

2188也是求逆序数,只不过给定两个序列,求这两个序列的逆序对。

做法只是需要先将其中的一个序列更名到1-n,然后就是纯粹的求逆序对。这里用树状数组求的,而且可以不用二分。先求出数组中元素对应的下标,然后从数组中最小的元素开始在树状数组中查找。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 1005
int n;
int s[N],t[N],flag[N],tree[N];
struct node{
    int a,b;
}p[N];
int cmp(node x,node y){
    return x.b < y.b;
}
int lowbit(int x){
    return x&(-x);
}
void add(int i,int x){
    for(int j = i;j<=n;j+=lowbit(j))
        tree[j] += x;
}
int sum(int i){
    int j,res= 0;
    for(j = i;j>=1;j-=lowbit(j))
        res += tree[j];
    return res;
}
int main(){
    int i,res=0;
    scanf("%d",&n);
    clr(tree, 0);
    for(i = 1;i<=n;i++){
        scanf("%d %d",&s[i],&t[i]);
        flag[s[i]] = p[i].a = i;
    }
    for(i = 1;i<=n;i++)
        p[i].b = flag[t[i]];
    sort(p+1,p+1+n,cmp);
    for(i = 1;i<=n;i++){
        res += sum(n)-sum(p[i].a);
        add(p[i].a,1);
    }
    printf("%d\n",res);
    return 0;
}


你可能感兴趣的:(poj 2299/2188 求逆序数个数(分治or线段树\树状数组))