猫猫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分
要记得按数值大小排序 如果相同就保持原来顺序(按原来下标升序)