洛谷:P1908 逆序对

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

思路

此题我用的树状数组,不懂的可以戳此链接树状数组

  1. 序列的当前位置之后的数如果比当前位置小,那么就会构成逆序对,比如5,4 ,3序列,第一个位置是5,第一个位置之后比5小的有4,3,所以构成了两对逆序对,枚举每一个位置,我们可以将先输入的数标记起来,如果后面输进来的数比标记的数小则就构成了逆序对,也就是在标记的数中找比后输进来的数大的个数,此时我们可以想到修改点+区间查询(树状数组)。
  2. 修改点就是标记点,区间查询就是查询比此位置的数大的在标记的点中有多少个。
  3. 注意数据,因为序列中每个数字不超过 1 0 9 10^9 109,数组开不了这么大,所以我们要离散化,至于怎么离散化,不懂的可以去了解一下。
  4. 另外要注意的是排序的时候,如果值一样,就不要交换位置了,交换了位置也就是交换了输入顺序,会增加情况的,不理解的可以输入有相同值的情况试试。

下面看代码吧!建议将排序后的数据输出来理解,不理解为什么离散化,可以输入一些大的值去理解。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int t[N];
struct node{
	int val,ind;
}stu[N];
int Rank[N]; //存储排序之后的位置,因为数太大了,所以要进行离散化
int n;
int lowbit(int x){return x&(-x);};
int cmp(node a,node b)
{
	if(a.val==b.val)   //值相等的时候位置不能变
		return a.ind<b.ind;
	return a.val<b.val;
}
void add(int pos)  //修改点
{
	for(int i=pos;i<=n;i+=lowbit(i)) t[i]+=1;
}
int search(int L,int R) //求区间和
{
	int s=0;
	for(int i=L;i;i-=lowbit(i)) s-=t[i];
	for(int i=R;i;i-=lowbit(i)) s+=t[i];
	return s;
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>stu[i].val;
		stu[i].ind=i;
	}
	sort(stu+1,stu+n+1,cmp); 
	for(int i=1;i<=n;i++)  //离散化
	{
		Rank[stu[i].ind]=i;
	}
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		int pos=Rank[i];
		ans+=search(pos,n);
		add(pos);
	}
	cout<<ans;
	return 0;
}
/*
输入:
6
5 4 2 6 3 1
输出:
11
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值