您的位置:首页 > 其它

逆序对(树状数组/归并)

2017-01-19 19:58 190 查看
题目描述

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

输出格式:给定序列中逆序对的数目。

输入输出样例

输入样例#1:

6

5
4 2 6 3 1

输出样例#1:

11

分析:不要在意题目为什么这么zz(从luogu上粘的。。。hh)

这个题的简单思路是归并,主体思路不变,但是在“并”的时候如果出现左边子段中的元素大于右子段的元素这种情况,显然右子段中的元素可以与剩下的左子段中的每一个元素都组成逆序对。光这么说应该是很难理解的,所以我们来举个例子:

若当前情况如下:

a1
a2 a3 a4 a5 a6

2
4 5 1 6 7

i
j

(因为归并是用递归实现的,所以可以保证左子端和右子段这单独的两部分是有序的)

看一下现在i和j所指的元素 a[i]=2,a[j]=1,此时a[i]>a[j],所以a[j]可以和左区间中从i到m(m=(左端点+右端点)/2)的每一个元素组成逆序对,逆序对个数是:m-i+1

明白了原理之后,看代码

#include<cstdio>
#include<iostream>
#include<cstring>

using namespace std;

int a[40010],b[40010],n,ans=0;

int merge(int l,int r)
{
int m=(l+r)/2;
int i=l,j=m+1; //i,j分别是该区间内左右两个子段的两起点
int k;
for (k=l;k<=r;k++)
{
if ((i<=m&&a[i]<=a[j])||j>r)
{
b[k]=a[i];
i++;
}
else
{
ans+=m-i+1; ///!!!
b[k]=a[j];
j++;
}
}
for (k=l;k<=r;k++)
a[k]=b[k];
return 0;
}

int sor(int l,int r)
{
if (l<r)
{
sor(l,(l+r)/2); //归并的基本框架
sor((l+r)/2+1,r);
merge(l,r); //说这就是所谓的“并”了
}
return 0;
}

int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
sor(1,n);
printf("%d",ans);
return 0;
}
上面这种解法时间在90ms左右

之后我有用树状数组写了一下,时间就到了140ms左右,估计这50ms是离散化的guo。

说一下主体思想:首先要把原始数列离散化,然后把数列从后往前扫,把当前的数字塞入树状数组(当前数字是在树状数组中的下标),之后求一下前缀和相加就可以了

代码如下

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;

int n,m;
int a[40010],c[40010],ans=0;
struct node{
int wz,num;
};
node ls[40010];

int com(const node &ha,const node &he)
{
if (ha.num<he.num) return 1;
return 0;
}

int ask(int x)
{
int i,sum=0;
for (i=x-1;i>0;i-=i&(-i))
sum+=c[i];
ans+=sum;
return 0;
}

int build(int x)
{
int i,j;
for (i=x;i<=n;i+=i&(-i))
c[i]++;
ask(x);
return 0;
}

int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&ls[i].num);
ls[i].wz=i;
}
memset(c,0,sizeof(c));
sort(ls+1,ls+1+n,com); //下面是离散化操作,可能有点笨,见谅
int r=0,tot=0;
for (int i=1;i<=n;i++)
{
if (ls[i].num!=r)
a[ls[i].wz]=++tot,r=ls[i].num;
else
a[ls[i].wz]=tot;
}
for (int i=n;i>=1;i--)
build(a[i]); //树状数组的基本操作
printf("%d",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: