首页 > 其他分享 >G - 逆序对的数量

G - 逆序对的数量

时间:2023-02-19 17:55:56浏览次数:68  
标签:10 int mid 数组 include 数量 逆序

G - 逆序对的数量

原题链接

什么是逆序对?

简单来说,两个数比较,下标小的数反而大,两个数称之为逆序对如\({3,1}\)就是这么一个逆序对

归并排序

由于逆序对逆序的性质,我们可以联想到排序:
排序的过程就是消除逆序对的过程,消除的次数就是逆序对的数量

归并排序的性质:每次划分后合并时左右子区间都是从小到大排好序的,我们只需要统计右边区间每一个数分别会与左边区间产生多少逆序对即可

注意

逆序对的个数最大的情况发生在整个数组逆序时即:

\[(n-1)+(n-2)+...+1 = \frac{n\cdot(n-1)}{2} \]

由于

\[n≤5×10^5 \]

答案是大于\(10^{10}\)的(会爆int)
注意要使用long long

代码1

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 5e5+10;
const int M = 2e5+10;
int n,m;
int q[N],temp[N];

LL merge_sort(int l,int r){
	if(l >= r)return 0;

	int mid = l + r >> 1;
	LL res = merge_sort(l,mid) + merge_sort(mid+1,r);

	int k = 0,i = l,j = mid + 1;
	while(i <= mid && j <= r){
		if(q[i] <= q[j])temp[++k] = q[i++];
		else{
			temp[++k] = q[j++];
			res += mid - i +1;
		}
	}
	while(i <= mid)temp[++k] = q[i++];
	while(j <= r)temp[++k] = q[j++];

	for(int i = l,k = 1; i <= r; i ++,k ++)q[i] = temp[k];
	return res;
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i ++ )cin >> q[i];
	cout << merge_sort(1,n) << nl;

}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}

树状数组

树状数组相关知识以及模板题1
树状数组相关知识以及模板题2

思路来源于暴力解法:从小到大枚举数组的每一个数(天然地形成逆序对\(j<i\)(j指的是先前的数,i指的是当前的数)的条件),此时我们只需要知道前面有多少个数比a_i(当前数)小即可,这里我们可以用到(就是一个数组cnt)来记录每个数的出现(怎么记录?每次枚举到这个数后,\(cnt[a_i]++\)即可)

然而,\(a_i\)的范围是\(10^9\),而\(n\)的范围却只有\(5\cdot10^5\),直接开cnt数组会mle

这时,我们又可以发现逆序对只跟相对大小有关系,所以我们的第一步优化便是将原数组离散化处理(排序+去重)得到每个数的相对大小,同时每次枚举时使用二分来找到每个数的相对大小即可

然而,此时我们又遇到一个问题,对于每次统计前面有多少个数比a_i(当前数)小又需要多次求和,暴力解又会tle

这时,我们又可以联想到多次求和有奇效的树状数组,通过树状数组来维护桶

答案显而易见

\[\sum_{i = 1}^{n} \ ( \ i \ - \ getsum(1,k) \ ) \]

\(getsum(1,k)\)为小于等于\(a_i\)的数的数量(等于的话不是逆序对)
\(i - getsum(1,k)\)得到的则时关于\(a_i\)逆序对的数量

代码

点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

#define X first
#define Y second

typedef pair<int,int> pii;
typedef long long LL;
const char nl = '\n';
const int N = 1e6+10;
const int M = 2e5+10;
int n,m;
int a[N],b[N];	//原数组,树状数组(维护桶)
vector<int> v;	//储存所有待离散化的值进行排序和去重
LL ans = 0;

int lowbit(int x){
	return x & -x;
}

void add(int k,int x){
	while(k <= n){
		b[k] += x;
		k += lowbit(k);
	}
}

LL getsum(int l,int r){
	LL s1 = 0;
	l --;
	while(l){
		s1 += b[l];
		l -= lowbit(l);
	}
	LL s2 = 0;
	while(r){
		s2 += b[r];
		r -= lowbit(r);
	}
	return s2 - s1;
}

int find(int x){	//找到a[i]在序列中排第几
	int l = 0,r = v.size() - 1;
	while(l < r){
		int mid = l + r >> 1;
		if(v[mid] >= x)r = mid;
		else l = mid + 1;
	}
	return l + 1;	//v从0开始
	//此处映射为1,2,3,...,n
}

void solve(){
	cin >> n;
	for(int i = 1; i <= n; i ++ ){
		cin >> a[i];
		v.push_back(a[i]);
	}

	sort(v.begin(),v.end());	//排序(从小到大)
	v.erase(unique(v.begin(),v.end()),v.end());	//去重
	// //离散化得到每个数的相对大小

	//枚举每个数找逆序对数量
	for(int i = 1; i <= n; i ++ ){
		int k = find(a[i]);
		add(k,1);	
		ans += (i - getsum(1,k));	//getsum(1,k)为小于等于a[i]的数的数量(等于的话不是逆序对)
	}
	cout << ans << nl;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);

	solve();
}

标签:10,int,mid,数组,include,数量,逆序
From: https://www.cnblogs.com/J-12045/p/17135208.html

相关文章

  • 代码随想录算法训练营第三十四天 | 860.柠檬水找零,406.根据身高重建队列,452. 用最少数
    一、参考资料柠檬水找零https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html根据身高重建队列https://programmercarl.com/040......
  • 【前端】Steam修复愿望单数量错误
    ✨Steam愿望单数量错误Steam显示的愿望单数量:46愿望单优先级排序:1~45即愿望单数量为45实际上是由于有的游戏已经从Steam商店删除所以在愿望单中不显示但是仍然计入......
  • AcWing788.逆序对的数量(Java)
    题目来源:https://www.acwing.com/problem/content/790/题目描述给定一个长度为n的整数数列,请你计算数列中的逆序对的数量。逆序对的定义如下:对于数列的第i个和第j......
  • 【DFS&并查集】岛屿数量
    经典的dfs/bfs问题,给一个起点开始搜索,满足条件则继续调用dfs/bfs从没有访问过的某个陆地出发,将所有能到达的陆地的状态都记录为已访问。下次出发不从已访问的陆地出发,每次......
  • C语言填空:利用指针形成逆序字符串函数
    #include<stdio.h>【1】//逆序输出任意字符串voidseverse_string(char【2】str){intlen=strlen(str);char*left=str;char*right=str+le......
  • C语言填空:利用数组形成逆序字符串的函数,不用指针
    #include<stdio.h>【1】//逆序输出任意字符串voidseverse_string(chararr【2】){intlen=strlen(arr);intleft=0;intright=len-1;......
  • C语言填空:逆序整数
    #include<stdio.h>//从键盘输入一个整数,如果不高于100则逆序输出,否则输出"输入范围错误"【1】nx(intn){while(n){printf("%d",【2】);......
  • C语言填空:逆序输出任意字符串
    #include<stdio.h>//逆序输出任意字符串【1】main(){charzf[100];inta,b;【2】;a=【3】;for(b=a-1;b>=0;b--)printf("%c",zf[b]);......
  • 传递任意数量的实参
    一丶有时候,你预先不知道函数需要接受几个实参,好在python允许从调用语句中收集任意数量的实参,例如,来看一个制作披萨的函数,他需要接受很多配料,但你无法预先确......
  • P1908 逆序对
    题目描述猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的......