返回顶部

逆序对

逆序对

1.归并算法

归并算法过程中,存在以下几种逆序对:

1)左区间内

2)右区间内

3)横跨两个区间

设归并数组已经排到第 \(k\) 个数

只考虑第三种逆序对的话,由归并排序的性质,我们先找到了已经分别单调的左右区间

若左区间的 \(a[i]\) 大于右区间的 \(a[j]\)

\(a[j]\) 和右区间内已经不会成逆序对了,而它和左区间中 \(a[i]\) 前的数也不可能是逆序对,否则在 \(a[i]\) 之前它就加入归并数组中了。

所以,\([str,i)\)\([mid+1,j)\) 不对逆序对做贡献,而这两个区间数的总和是k个(因为 \(i\)\(j\) 是从 \(str\)\(mid\) 随着 \(k++\)上去的,\(i\)\(j++\) 的总值就是 \(k++\) 的总值)所以从 \(str\)\(j\) 做出贡献的数有 \(j-k\) 个, \(ans+=j-k\)

对于第一和第二种情况,它们在更小的归并操作中已经在第三种情况中被处理,所以不需要考虑

代码

void merge(ll s,ll t)
{
    ll mid,i,j,k;
    if(s==t) return;
    mid = (s + t)>>1;
    merge(s,mid);
    merge(mid+1,t);
    i=s,j=mid+1,k=s;
    while(i<=m && j<=t)
    {
        if(a[i]<=a[j])
        {
            r[k]=a[i]; i++, k++;
        }
        else
        {
            r[k]=a[j]; 
            ans += j-k;//核心代码
            j++,k++;
        }
    }
    while(i<=m)
    {
        r[k]=a[i];i++,k++;
    }
    while(j<=t)
    {
        r[k]=a[j];j++,k++;
    }
    for(int i=s;i<=t;++i) a[i]=r[i];//归并后放回原数组
    
}

2.树状数组

首先明白一个概念叫做离散化(Discretization)

树状数组中,只需要开一个与原序列中最大元素相等的长度数组就行,那么如果我的序列是1,5,3,8,999,本来5个元素,却需要开到999这么大,造成了巨大的空间浪费,

离散化就是另开一个数组,d, d[i]用来存放第i大的数在原序列的位置,比如原序列a={5,3,4,2,1},第一大就是5,他在a中的位是1,所以d[1]=1,同理d[2]=3,········所以d数组为{1,3,2,4,5}。

这样d中的正序等价于原数组的逆序

来看一下怎么求的:

首先把1放到树状数组t中,此时t只有一个数1,t中比1小的数没有,sum+=0
再把3放到树状数组t中,此时t只有两个数1,3,比3小的数只有一个,sum+=1
把2放到树状数组t中,此时t只有两个数1,2,3,比2小的数只有一个,sum+=1
把4放到树状数组t中,此时t只有两个数1,2,3,4,比4小的数有三个,sum+=3
把5放到树状数组t中,此时t只有两个数1,2,3,4,5,比5小的数有四个,sum+=4
最后算出来,总共有9个逆序对,可以手算一下原序列a,也是9个逆序对.

每次放入一个数的时候,是更新这个数的个数

查到 \(d[i]\) 时,找的就是 \(d[i]\) 前的所有数次数的前缀和,就可以用树状数组维护

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=5e5+7;
int lowbit(int x)
{
	return x&(-x);
}
int a[maxn],d[maxn],t[maxn],n,ans;

void add(int x)
{
	for(x=x;x<=n;x+=lowbit(x))
	{
		t[x]++;
	}
}
int query(int x)
{
	int res=0;
	for(x=x;x>0;x-=lowbit(x))
	{
		res+=t[x];
	}
	return res;
}


bool cmp(int x,int y)
{
	if(a[x]==a[y]) return x>y;
	return a[x]>a[y];
}

signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];d[i]=i;
	}
	sort(d+1,d+1+n,cmp);//离散化重排序
	                    //d中第i个指第i大的数在a中位置
	for(int i=1;i<=n;++i)
	{
		add(d[i]);
		ans += query(d[i]-1);
	} 
	cout<<ans;
	return 0;
} 
posted @ 2022-06-29 21:08  魔幻世界魔幻人生  阅读(52)  评论(0)    收藏  举报