树状数组和归并算法求逆序对

引入

什么是逆序对?
概念:对于一对数字 a i , a j ai, aj ai,aj,当 i < j i<j i<j a i > a j ai>aj ai>aj时,称这一对数字为逆序对。

对于求逆序对而言,一般是有两种不同的解决方法,其一是树状数组,其二是归并排序。(我个人比较倾向于树状数组,毕竟是很早以前就学过了的算法,现在复习一遍印象深刻,但是归并就学的太早了,现在复习也是很模糊。。。)

树状数组求逆序对

对于一个序列 a a a而言,我们可以把 a a a数组的数值范围当做树状数组维护的值。

  1. 倒序初始化插入 a a a序列的值,这样子的话就可以保证在插入 i i i的时候树状数组中就只有 i i i和他后面的数值,而没有他前面的。
  2. 这样子的话,就在插入 a i ai ai的时候查找一下在树状数组中 a i ai ai之前是否有值,有几个值就是 a i ai ai构成的逆序对数。

重点:

  1. 数值范围作为树状数组维护的值;
  2. 倒序插入。

参考例题:https://www.luogu.org/problemnew/show/P1908

code

//树状数组求逆序对 
#include <bits/stdc++.h>
using namespace std;

const int nn=5e5+5;
typedef long long ll;

int n;
int maxx=-1000;
int a[nn],b[nn];
ll c[nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

void read_d()//离散化 
{
	n=read();
	for(int i=1;i<=n;++i)	a[i]=read(),b[i]=a[i];
	sort(b+1,b+1+n);
	int lb=unique(b+1,b+1+n)-(b+1);
	for(int i=1;i<=n;++i)
		a[i]=lower_bound(b+1,b+1+lb,a[i])-b;
}

void add(int x,int y)
{
	for( ;x<=n;x+=x&(-x))
		c[x]+=y;
}

ll ask(int x)
{
	ll sum=0;
	for( ;x>0;x-=x&(-x))/*【2019/9/27】注意这里的树状数组的操作不可以是>=0的*/
		sum+=c[x];
	return sum;
}

int main()
{
	read_d();/*离散化*/
	ll ans=0;
	for(int i=n;i>=1;--i)/*倒序处理*/
	{
		add(a[i],1);
		ans+=ask(a[i]-1);/*找到a[i]之前的数字,并不包括a[i]*/
	}
	printf("%lld\n",ans);/*注意最后结果要开longlong*/
	return 0;
}

归并排序

归并的简单理解

首先,我们来谈一下,什么是归并排序?

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。

上面的内容转自百度百科

简单来说,就是我们先把一个无序的序列分成若干个小区间,在每个的小区间内,我们保证有序,之后我们考虑把两个有序的小区间合并成一个有序的大区间。这种算法就叫做归并排序

通过下面的图片(图片转载自[图解] 归并排序),我们简单理解一下归并排序的过程。
在这里插入图片描述


归并排序为什么可以求逆序对?

可以从上面的图片中看出,在后面的区间中的数字从后面被安排到前面的时候,该数字之前的位置到该数字现在的之中的数字个数就是该数字的逆序对数。

那么,我们就可以在对一个序列进行归并排序的同时统计:在合并两个区间的同时该数字,是否是有后面的区间中的数字提到前面而来,之后我们统计中间跨越了多少个数字进而求出逆序对的个数。

附一道题的代码:奇数码问题

#include<bits/stdc++.h>
using namespace std;

int n, N;
vector<int> a[2];
int c[250003];
int sum1=0, sum2=0, ans=0;

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
}

void merge(int k,int l,int mid,int r)
{
	int x=l,y=mid+1;/*指针*/
	for(int i=l;i<=r;++i)
	{
		if((x<=mid&&a[k][x]<a[k][y])||y>r)	c[i]=a[k][x++];
		else	ans+=mid-x+1, c[i]=a[k][y++];
	}
	for(int i=l;i<=r;++i)	a[k][i]=c[i];
}

void mergesort(int k,int l,int r)
{
	if(l==r)	return ;
	int mid=(l+r)>>1;
	mergesort(k,l,mid);
	mergesort(k,mid+1,r);
	merge(k,l,mid,r);
}

int main()
{
	while(cin>>n)
	{
		N=n*n; sum1=0, sum2=0;
		a[0].clear(), a[1].clear();
		for(int i=1;i<=N;++i)	
		{
			int x=read();
			if(x)	a[0].push_back(x);
		}
		for(int i=1;i<=N;++i)
		{
			int x=read();
			if(x)	a[1].push_back(x); 
		}
		if(!a[0].size()){puts("TAK"); continue;}/*判断特殊情况*/
		ans=0;
		mergesort(0, 0, a[0].size()-1);
		sum1=ans;/*统计第一个的逆序对的个数*/
		ans=0;
		mergesort(1, 0, a[1].size()-1);
		sum2=ans;
		if(sum1%2==sum2%2)	puts("TAK");
		else puts("NIE");
	}
	return 0;
}

小注:上面的链接如有侵权,请联系博主,立即删除。
完……

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值