经典题:用树状数组求逆序数+离散化(3743)

Frosh Week

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2677    Accepted Submission(s): 888


Problem Description
During Frosh Week, students play various fun games to get to know each other and compete against other teams. In one such game, all the frosh on a team stand in a line, and are then asked to arrange themselves according to some criterion, such as their height, their birth date, or their student number. This rearrangement of the line must be accomplished only by successively swapping pairs of consecutive students. The team that finishes fastest wins. Thus, in order to win, you would like to minimize the number of swaps required.
 

Input
The first line of input contains one positive integer n, the number of students on the team, which will be no more than one million. The following n lines each contain one integer, the student number of each student on the team. No student number will appear more than once.
 

Output
Output a line containing the minimum number of swaps required to arrange the students in increasing order by student number.
 

Sample Input
  
  
3 3 1 2
 

Sample Output
  
  
2


首先,这道题实质是求逆序数(最小步数排成一队->逆序数(在只能相邻交换的情况下成立,如果能跨越,就不是求逆序数了!2016.3.20 蓝桥杯省赛));其次这道题目的本质思想是把原数组a映射到另一个数组的下标,然后每放入一个,查找在它后面的数有几个,这个个数就是此位置逆序数,由于暴力查找效率过低使用树状数组维护这一个数组,便于得到前缀和,这个位置的逆序数就是getsum(n)-getsum(a[i].num);另外一个问题是:原数组跨度可能非常大,为了优化空间复杂度,使用离散化(只适用于只关注数据相对大小,不关注具体值的情况),这里还使用了一个小技巧:给数组的每个元素一个id,因为sort函数会改变原数组的相对位置,所以改变之后可以根据id恢复回来;最后扫描一下就可以了,注意逆序数需要long long保存。


/*------------------Header Files------------------*/
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <stack>
#include <queue>
#include <map>
#include <vector>
#include <limits.h>
using namespace std;
/*------------------Definitions-------------------*/
#define LL long long
#define PI acos(-1.0)
#define INF 0x3F3F3F3F
#define MOD 10E9+7
#define MAX 1000050
/*---------------------Work-----------------------*/
struct node
{
	int id,num;	
}a[MAX];
int tree[MAX],n;
LL cnt; //逆序对计数
bool cmp1(node a,node b)
{
	return a.num<b.num;
}
bool cmp2(node a,node b)
{
	return a.id<b.id;
}
int lowbit(int i)
{
	return i&-i;
}
void update(int i,int num)
{
	while(i<=n)
	{
		tree[i]+=num;
		i+=lowbit(i);
	}
}
int getsum(int i)
{
	int sum=0;
	while(i>=1)
	{
		sum+=tree[i];
		i-=lowbit(i);
	}
	return sum;
}
void work()
{
	while(scanf("%d",&n)==1)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i].num);
			a[i].id=i;
		}
		sort(a+1,a+n+1,cmp1);
		for(int i=1;i<=n;i++) //离散化处理
			a[i].num=i; //不会出现重复数字的情况
		sort(a+1,a+n+1,cmp2); //恢复原来顺序
		memset(tree,0,sizeof(tree));
		cnt=0;
		//for(int i=1;i<=n;i++)
		//	printf("%d ",a[i].num);
		for(int i=1;i<=n;i++)
		{
			update(a[i].num,1);
			cnt+=getsum(n)-getsum(a[i].num);
		}
		//for(int i=1;i<=n;i++)
		//	printf("%d\n",tree[i]);
		printf("%I64d\n",cnt);
	}
}
/*------------------Main Function------------------*/
int main()
{
	//freopen("test.txt","r",stdin);
	//freopen("cowtour.out","w",stdout);
	//freopen("cowtour.in","r",stdin);
	work();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组(Fenwick tree)是一种用于高效解数列前缀和(Prefix Sum)问的数据结构,而逆序对也是一种经典的问。 假设有个长度为 n 的数组 a,我们要其中逆序对的数量,即所有 i < j 且 a[i] > a[j] 的 (i,j) 对数。这个问可以用归并排序的思想解,但是这里我们介绍一种使用树状数组的解法。 思路如下: 1. 将原数组 a 复制一份并排序,得到新数组 b,将 b 中的每个元素在原数组 a 中的下标记录在数组 c 中。 2. 从右往左遍历 a,对于每个元素 a[i],在树状数组中查询前缀和 sum(c[i]-1),即小于 a[i] 的元素个数,将结果累加到逆序对计数器 ans 中。 3. 将 a[i] 在数组 c 中对应的位置在树状数组中更新为 1。 下面是使用 Python 实现的代码: ```python def lowbit(x): return x & -x def update(bit, x, val): n = len(bit) while x <= n: bit[x] += val x += lowbit(x) def query(bit, x): res = 0 while x > 0: res += bit[x] x -= lowbit(x) return res def count_inversions(a): n = len(a) b = sorted(a) c = {v: i+1 for i, v in enumerate(b)} bit = [0] * (n+1) ans = 0 for i in range(n-1, -1, -1): ans += query(bit, c[a[i]]-1) update(bit, c[a[i]], 1) return ans ``` 其中,lowbit 函数是计算 x 的最低位 1 所代表的值;update 函数是树状数组的更新操作;query 函数是树状数组的查询操作;count_inversions 函数是主函数,用于计算逆序对数量。 时间复杂度为 O(nlogn)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值