刷题记录:NC208813求逆序数

传送门:牛客

题目描述:

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为
一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。
比如一个元素个数为4的数列,其元素为2,4,3,1,则(2,1),(4,3),(4,1),(3,1)是逆序,逆序数是4
现在求给定数列的逆序数
输入:
4
2 4 3 1
输出:
4

主要思路:

这道题主义是去求逆序对的一道题,首先这道题暴力也是可以很轻松的解决的,因此难度是不高的,暴力代码如下

#include <stdio.h>
int main(){
    int n, cnt = 0;
    scanf("%d", &n);
    int a[n];
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    for(int i = 0; i < n - 1; i++){
        for(int j = i + 1; j < n; j++) {
            if(a[j] < a[i])cnt++;
        }
    }
    printf("%d", cnt);
    return 0;
}

但是显然这个代码是可以继续优化的,请移步如下逆序对,你会发现之前写的代码不出所料的Tie掉了,然后你感到很郁闷.然后就会发现了用归并排序来优化这道题

如果你完全不会归并排序,可以移步百度进一步学习,如果你之前有所了解,可以移步这里去稍微复习一遍,或许可以唤醒你的记忆

然后我们举个栗子:
比如
5 5 6(在左边) 和1 1 9(在右边)这两个序列(使用归并排序进行二分了)
然后我们使用归并排序进行合并时就会发现1碰到当前的5是比5小的,也就是此时的1是合并到新序列中去的,而此时左边剩下3个数字,也就是此时对于1来说存在3个逆序对,同理,右边的第二个1也有3个逆序对,而9就没有逆序对了

也就是说当我们去执行归并排序中的这个代码时:

	while(i<=mid&&j<=r) {
		if(a[i]<a[j]) {//将两个组合中的较大的值放入新的组合的前列
			fuzhu[k++]=a[i++];
		}else {
			fuzhu[k++]=a[j++];
		}
	}

当我们发现a[i]>a[j]时就会产生逆序对了,而此时产生的逆序对的数量恰好是左边序列中剩余的数字的个数(即mid-i+1),因为只有右边进行合并的数字比左边的任何一个数字都要小时才会合并到新的序列中的,至此我们就得到了使用归并排序来求逆序列的方法了

下面是具体代码(只比归并排序多了一句代码):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
int ans=0;
int a[3000],fuzhu[3000];
void qsort(int l,int r) {
	if(l>=r) return ;
	int mid=(l+r)>>1;
	qsort(l,mid);qsort(mid+1,r);
	int k=l,i=l,j=mid+1;
	while(i<=mid&&j<=r) {
		if(a[i]<=a[j]) fuzhu[k++]=a[i++];
		else {
			fuzhu[k++]=a[j++];
			ans+=(mid-i+1);
		}
	}
	while(i<=mid) fuzhu[k++]=a[i++];
	while(j<=r) fuzhu[k++]=a[j++];
	for(int ii=l;ii<=r;ii++) a[ii]=fuzhu[ii];
}
int main() {
	int n;n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	qsort(1,n);
	cout<<ans<<endl;
	return 0;
}

当然求逆序对还有更加NB装逼的方法,你可以使用树状数组,甚至是线段树的方法进行求值,但是我现在还没复习到(唉,明明之前是会的,现在却…),就不献丑了,之后补坑.


update in 2023.02(已经补完线段树)

泪目了,没想到如今的我居然能够轻松的独立写出线段树代码了,并且已经完全懂了如何使用离散化的操作.想想当初高中OI时期即使学了线段树,打了不少题,还是十分懵懂的,线段树上的离散化则是完全不理解. 想了这么多,泪不禁要流了下来.

对于一个位置 j j j来说,他如果能与之前的位置 i i i形成逆序对,意味着 a [ i ] > a [ j ] a[i]>a[j] a[i]>a[j],那么逆序对的个数实际上就是 j j j之前所有大于 a [ j ] a[j] a[j] a [ i ] a[i] a[i]的个数,对于这个我们可以直接使用线段树进行解决.

我们可以使用线段树维护目前位置 a [ i ] a[i] a[i]出现的次数.类似于桶的方法,每当 a [ i ] a[i] a[i]出现一次,就将 a [ i ] a[i] a[i]位置的值+1,然后对于我们的 j j j来说,我们只要用线段树来查询大于 a [ j ] a[j] a[j]所有位置的和即可.因为对于 a [ j ] a[j] a[j]来说,目前线段树中维护的所有值肯定都是在 a [ j ] a[j] a[j]之前出现过的.那么只要是大于 a [ j ] a[j] a[j]的肯定就是我们的逆序对了

对于此题来说,我们发现 l , r l,r l,r i n t int int范围内,但是我们无法直接使用线段树来维护这么大的区间,所以我们需要进行离散化操作.对于离散化操作,网上对此有大量的博客,篇幅都较长,也不乏有非常详细的,所以此处就不在赘述了

下面是具体的代码部分:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define int long long
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Segment_tree{
	int l,r,sum;
}tree[maxn*4];
int n;int a[maxn];
void pushup(int rt) {
	tree[rt].sum=tree[ls].sum+tree[rs].sum;
}
void build(int l,int r,int rt) {
	tree[rt].l=l;tree[rt].r=r;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(lson);build(rson);
	pushup(rt);
}
void update(int pos,int rt,int v) {
	if(tree[rt].l==pos&&tree[rt].r==pos) {
		tree[rt].sum+=v;
		return ;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(pos<=mid) update(pos,ls,v);
	else update(pos,rs,v);
	pushup(rt);
}
int query(int l,int r,int rt) {
	if(tree[rt].l==l&&tree[rt].r==r) {
		return tree[rt].sum;
	}
	int mid=(tree[rt].l+tree[rt].r)>>1;
	if(r<=mid) return query(l,r,ls);
	else if(l>mid) return query(l,r,rs);
	else return query(l,mid,ls)+query(mid+1,r,rs);
}
vector<int>v;
signed main() {
	n=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
		v.push_back(a[i]);
	}
	//进行离散化操作
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	int Size=v.size();
	build(1,Size+10,1);//加10是为了操作过程中产生越界的情况,这个10可以是任意数字
	int ans=0;
	for(int i=1;i<=n;i++) {
		int x=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
		ans+=query(x+1,Size+10,1);
		update(x,1,1);
	}
	cout<<ans<<endl;
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值