逆序对模板

P1908 逆序对

猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。
Update:数据已加强。

输入格式
第一行,一个数n,表示序列中有n个数。

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

输出格式
给定序列中逆序对的数目
输入输出样例
输入 #1

6
5 4 2 6 3 1

输出 #1

11

说明/提示
对于25%的数据,n≤2500

对于50%的数据,n≤4×104

对于所有数据,n<=5×105

请使用较快的输入输出

应该不会n方过50万吧 by chen_zhe

题解:

我们可以先开一个大小为和输入数组一样大的t[i]记录:1-a[i]区间上比a[i]小的个数的数组t,并用树状数组维护它,每当读入一个数时,将t[a[i]]加上1,然后我们统计t[1]~t[a[i]]的和ans,ans就是在这个数前面有多少个比它的个数。我们只要用i-ans就可以得出前面有多少数比它大,也就是逆序对的数量注意树状数组的长度为输入数组中最大值,很多情况下都会爆数组空间,如果使用排序离散化后,数组的长度就可以缩短为n(输入数据的个数)

现给出树状数组未离散版本和排序后离散两个版本

**一、未使用排序离散,**使用于比如范围为“1~n”的数组求逆序对
(注意:本题不行!)

//本题:第二行n个数,表示给定的序列。序列中每个数字不超过10^9
//10的9次放!!!不用想了,	老实的排序离散化吧 
#include<bits/stdc++.h>
using namespace std;
int n;
int tree[50005];
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,int k)//单点修改 
{
	for(int i=x;i<=n;i+=lowbit(i))
	tree[i]+=k;
	return;
}
int ask(int x)//单点查询前缀和 
{
	int ans=0;
	for(int i=x;i>=1;i-=lowbit(i))
	ans+=tree[i];
	return ans;
}
int main(void)
{
	int t,x;
	scanf("%d",&t);
	while(t--)
	{
		int ans=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&x);
			add(x,1);
			//ask(x)查询x在【0,i】区间小于x的个数 
			ans+=i-1-(ask(x)-1);//x的逆序对个数=当前位置i- 在【0,i】区间小于x的个数 =在【0,i】区间大于x的个数 
		}
		printf("%d\n",ans); 
	}
	return 0;
}

二、使用离散化,当输入数中的最大值太大时(一般大于106)适用。
比如本题中:“序列中每个数字不超过109”,109!!!数组开不了这么大的空间。

离散化似乎很高级?!其实就是比上面多了一个数组+排序

代码如下:

 #include<bits/stdc++.h>
using namespace std;
#define ll long long int
#define sizen 500005
struct node{
	int p;//秩 
	ll v;//本身数值 
}q[sizen];
bool operator < (struct node A,struct node B)
{
	if(A.v!=B.v )
	return A.v<B.v;//有重复数,但是它们先后顺序不一样,故不能只写这一行 
	return A.p<B.p;
}//按数值大小排序 如果相同就保持原来顺序(按原来下标升序) 
int n;
ll tree[sizen];//树状数组
int a[sizen];//离散后的数!!! 
int lowbit(int x)
{
	return x&(-x);
}
void add(int x,ll k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	tree[i]+=k;
	return;
}
ll ask(int x)//查询前缀和 
{
	ll ans=0ll;
	for(int i=x;i>=1;i-=lowbit(i))
	ans+=tree[i];
	return ans;
}
int main(void)
{
	int t;
	//scanf("%d",&t);
	//while(t--)
	//{
		ll ans=0ll;
		scanf("%d",&n);
		for(int i=0;i<=n;i++)
		tree[i]=0ll;//初始化 
		
		for(int i=1;i<=n;i++)
		scanf("%lld",&q[i].v ),q[i].p=i ;
		sort(q+1,q+n+1);//排序
		//离散化 
		for(int i=1;i<=n;i++)
		a[q[i].p ]=i; //修改原序列的下标对应的值,缩短空间 
		
		for(int i=1;i<=n;i++){
			//现在不a不是输入数组 ,而是用树状数组维护 离散后的数组 
			add(a[i],1);
			ans+=i-ask(a[i]);//前缀和 ,逆序个数 
		}
		printf("%lld\n",ans);
	//}
	return 0;
} 

在重载排序时,没有想全面只得40分
要记得按数值大小排序 如果相同就保持原来顺序(按原来下标升序)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值