HDU 1394 Minimum Inversion Number(最小逆序数/暴力 线段树 树状数组 归并排序)

题目链接: 传送门

Minimum Inversion Number

Time Limit: 1000MS     Memory Limit: 32768 K

Description

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 Iutput

10
1 3 6 9 0 8 5 7 4 2

Sample Output

16

解题思路

  • 1、暴力
    注意到输入的n个数是从0~n-1并且每次都把第一个数移到最后一个数,所以原来比它小的数(和它构成逆序)在移动之后就不是逆序了,而原来比它大的数>(不和它构成逆序)在移动之后就是逆序了,因此很容易推得每次减少的逆序数为n-1-a[i]每次增加的逆序数为a[i]
  • 2、线段树
    首先先来看一个序列 6 1 2 7 3 4 8 5,此序列的逆序数为5+3+1=9。冒泡法可以直接枚举出逆序数,但是时间复杂度太高O(n^2)。冒泡排序的原理是枚举每一个数组,然后找出这个数后面有多少个数是小于这个数的,小于它逆序数+1。仔细想一下,如果我们不用枚举这个数后面的所有数,而是直接得到小于这个数的个数,那么效率将会大大提高。
    总共有N个数,如何判断第i+1个数到最后一个数之间有多少个数小于第i个数呢?不妨假设有一个区间 [1,N],只需要判断区间[i+1,N]之间有多少个数小于第i个数。如果我们把总区间初始化为0,然后把第i个数之前出现过的数都在相应的区间把它的值定为1,那么问题就转换成了[i+1,N]值的总和。再仔细想一下,区间[1,i]的值+区间[i+1,N]的值=区间[1,N]的值(i已经标记为1),所以区间[i+1,N]值的总和等于N-[1,i]的值!因为总共有N个数,不是比它小就是比它(大或等于)。
    现在问题已经转化成了区间问题,枚举每个数,然后查询这个数前面的区间值的总和,N-[1,i]即为逆序数。

暴力求解

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef __int64 LL;

int main()
{
	int N;
	while (~scanf("%d",&N))
	{
		int ans[5005] = {0};
		int sum = 0,res;
		for (int i = 0;i < N;i++)
		{
			scanf("%d",&ans[i]);
		}
		for (int i = 0;i < N;i++)
		{
			for (int j = i + 1;j < N;j++)
			{
				if (ans[i] > ans[j])
				{
					sum++;
				}
			}
		}
		res = sum;
		for (int i = 0;i < N;i++)
		{
			sum += N - ans[i] - ans[i] - 1;
			res = min(res,sum);
		}
		printf("%d\n",res);
	}
	return 0;
}

线段树

#include<cstdio>
#include<algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1, r , rt << 1 | 1
const int maxn = 5005;
int sum[maxn<<2];

void PushUp(int rt)
{
	sum[rt] = sum[rt<<1] + sum[rt<<1|1];
} 

void build(int l,int r,int rt)
{
	sum[rt] = 0;
	if (l == r)	return;
	int m = (l + r) >> 1;
	build(lson);
	build(rson);
}


void upd(int p,int l,int r,int rt)
{
	if (l == r)
	{
		sum[rt]++;
		return;
	}
	int m = (l + r) >> 1;
	if (p <= m)	upd(p,lson);
	else	upd(p,rson);
	PushUp(rt);
}

int qry(int L,int R,int l,int r,int rt)
{
	if (L <= l && r <= R)
	{
		return sum[rt];
	}
	int m = (l + r) >> 1;
	int ret = 0;
	if (L <= m)	ret += qry(L,R,lson);
	if (R > m)	ret += qry(L,R,rson);
	return ret;
}

int main()
{
	int N;
	while (~scanf("%d",&N))
	{
		int ans[maxn];
		build(0,N - 1,1);
		int sum = 0;
		for (int i = 0; i < N;i++)
		{
			scanf("%d",&ans[i]);
			sum += qry(ans[i],N - 1,0,N - 1,1);
			upd(ans[i],0,N - 1,1); 
		}
		int ret = sum;
		for (int i = 0;i < N;i++)
		{
			sum += N - ans[i] - ans[i] - 1;
			ret = min(ret,sum);
		}
		printf("%d\n",ret);
	}
	return 0;
}

树状数组

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 5005;
int c[maxn],N;

void upd(int i,int v)
{
	while (i <= N)
	{
		c[i] += v;
		i += i & -i;
	}
}

int sum(int i)
{
	int ret = 0;
	while (i > 0)
	{
		ret += c[i];
		i -= i & -i; 
	}
	return ret;
}

int main()
{
	while (~scanf("%d",&N))
	{
		int ans[maxn] = {0};
		int res = 0,tmp;
		memset(c,0,sizeof(c)); 
		for (int i = 0;i < N;i++)
		{
			scanf("%d",&ans[i]); 
			res += sum(N) - sum(ans[i] + 1);
			upd(ans[i] + 1,1);	
		}
		tmp = res;
		for (int i = 0;i < N;i++)
		{
			tmp -= ans[i];
			tmp += N - ans[i] - 1;
			res = min(tmp,res);
		}
		printf("%d\n",res);
	}
	return 0;
}

归并排序

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int maxn = 5005;
int sum;
void merge_array(int array[],int left,int mid,int right)
{
	if (left >= right)	return;
	int i = left,j = mid + 1,k = 0;
	int *p;
	p = (int *)malloc((right - left + 1)*sizeof(int));
	while (i <= mid && j <= right)
	{
		if (array[i] <= array[j])
		{
			p[k++] = array[i++];
		}
		else
		{
			p[k++] = array[j++];
			sum += mid - i + 1;            //[i-mid]序列就都能与a[j]构成逆序对,故:mid-i+1
		}
	}
	while (i <= mid)
	{
		p[k++] = array[i++];
	}
	while (j <= right)
	{
		p[k++] = array[j++];
	}
	for (int i = 0;i < k;i++)
	{
		array[i+left] = p[i];
	}
	free(p);
}

void merge_sort(int array[],int left,int right)
{
	if (left >= right)	return;
	int mid = (left + right)>>1;
	merge_sort(array,left,mid);
	merge_sort(array,mid + 1,right);
	merge_array(array,left,mid,right);
}

int main()
{
	int N;
	while (~scanf("%d",&N))
	{
		int a[maxn],b[maxn],res;
		sum = 0;
		for (int i = 0;i < N;i++)
		{
			scanf("%d",&a[i]);
			b[i] = a[i];
		}
		merge_sort(a,0,N - 1);
		res = sum;
		for (int i = 0;i < N;i++)
		{
			sum += N - b[i] - b[i] - 1;
			res = min(res,sum);
		}
		printf("%d\n",res);
	}
	return 0;
}
posted @   zxzhang  阅读(256)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示

目录导航