Minimum Inversion Number HDU - 1394(线段树求逆序数)

Minimum Inversion Number

题目链接: HDU - 1394
求逆序数的方法有多种, 一可以直接暴力求, O(n^2)的复杂度, 二可以用归并排序求, O(nlogn)的复杂度,
现在学了线段树又可以用线段树求, O(nlogn)的复杂度;
前两种方法就不多说了, 就只谈用线段树求逆序数的方法;
9 2 8 3 6 7 5 这个序列的逆序数是12,是怎么出来的呢?
先看9, 前边没有比他大的数, 贡献0,
2, 前边有一个比它大的数, 贡献1,
8, 贡献1;3贡献2;6贡献2;7贡献2;5贡献4,合计12;
由此可看出只需要找出写下每个数之前有几个比他大的数已写即可;
我们可以先建个空的线段树, 节点为1~n, 每次写下一个数之前查询该数所在位置后边有几个数被写, 然后再记录该数;
这个题要求求出所给序列中按不同排列次序最小的逆序数是多少, 如2 8 7 4 3 的不同排列次序可以是8 7 4 3 2 或7 4 3 2 8。。。。。。
若一个序列中是0~n-1时:
当一个数i在序列首时, 序列中比他小的数只有i个, 当他移到序列尾时, 前边比他大得数有n-i-1个;
所以每次转移逆序数变化为sum=sum-i+n-1-i;
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 5010;
int n;
int a[maxn];
struct node{
	int left, right, x;
}tree[maxn<<2];
void build(int m, int l, int r){
	tree[m].left=l;
	tree[m].right=r;
	if(l==r){
		tree[m].x=0;
		return;
	}
	int mid=(l+r)>>1;
	build(m<<1, l, mid);
	build(m<<1|1, mid+1, r);
	tree[m].x=tree[m<<1].x+tree[m<<1|1].x;
}
void update(int m, int a){
	if(tree[m].left==a&&tree[m].right==a){
		tree[m].x++;
		return;
	}
	int mid=(tree[m].left+tree[m].right)>>1;
	if(a<=mid) update(m<<1, a);
	else update(m<<1|1, a);
	tree[m].x=tree[m<<1].x+tree[m<<1|1].x;
}
int query(int m, int l, int r){
	if(tree[m].left==l&&tree[m].right==r){
		return tree[m].x;
	}
	int sum1=0, sum2=0;
	int mid=(tree[m].left+tree[m].right)>>1;
	if(r<=mid) return query(m<<1, l, r);
	else if(l>mid) return query(m<<1|1, l, r);
	return query(m<<1, l, mid)+query(m<<1|1, mid+1, r);
}
int cnt[maxn], sum=0;
int main(){
	while(~scanf("%d", &n)){
		build(1, 1, n);
		sum=0;
		for(int i=1; i<=n; i++){
			scanf("%d", &a[i]);
			sum+=query(1, a[i]+1, n);//先查询再更新;因为输入值为0~n-1, 节点值为1~n, 所以向后错一个位置;
			update(1, a[i]+1);
		}
		int ans=sum;
		for(int i=1; i<=n; i++){
			//printf("i:%d   sum:%d\n", i, sum);
			sum=sum-a[i]+n-a[i]-1;
			ans=min(ans, sum);	
		}
		printf("%d\n", ans);
	}
	return 0;
}



猜你喜欢

转载自blog.csdn.net/sirius_han/article/details/80151686