逆序数的应用

知乎学习归并排序

Inverse Pair

贪心构造c数组,然后求逆序数就完了

#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
const int maxn=5e5+5;
int n;
LL a[maxn],b[maxn];//原数组和排序后的数组
LL pos[maxn]; 
LL ms(int l,int r)// 求数组a的逆序数的板子
{
    if(l >= r) return 0;
    int m = (l + r) >> 1;// 分治
    LL t1 = ms(l, m), t2 = ms(m + 1, r);//获得前半部分 和 后半部分 的逆序对个数 
    LL t3 = 0;//两边归并的逆序对个数 
    int i = l, j = m + 1, p = l;//前半部分起点、终点和当前排序的编号 
    while(i <= m && j <= r)//进行排序操作 
	{
        if(a[i] > a[j])//如果前部分的某个元素大于后半部分某个元素 
		{
            b[p++] = a[j++];//排序 
            t3 += m - i + 1;//因为前m元素中:i后的元素都比a[i]大,所以也比a[j]大,所以逆序对有m-i+1(包括自己)个 
        }
        else b[p++] = a[i++];//排序 
    }
    //完成剩下的归并操作 
    while(i <= m) b[p++] = a[i++];
    while(j <= r) b[p++] = a[j++];
    for(int i = l; i <= r; i++) a[i] = b[i];//将排序的值返回给a 
    return t1 + t2 + t3;//返回 
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
    	scanf("%lld", &a[i]);
		if(pos[a[i] + 1]) ++a[i];
		pos[a[i]] = i;
	}
    printf("%lld",ms(1,n));
    return 0;
}

洛谷-[NOIP2013 提高组] 火柴排队

结论:把一个无序的序列变成有序的序列,每次操作可以交换相邻两个数,最少需要的操作次数是这个序列的逆序数

一个数 x x x 对逆序数的贡献最多是 x − 1 x-1 x1
求逆序数利用桶排序的思想,遍历 A [ i ] A[i] A[i],我们每次先用 g e t s u m ( A [ i ] − 1 ) getsum(A[i]-1) getsum(A[i]1) 求出 [ 1 , A [ i ] − 1 ] [1,A[i]-1] [1,A[i]1] 有多少个数已经出现了,然后 ( A [ i ] − 1 ) − g e t s u m ( A [ i ] − 1 ) (A[i]-1)-getsum(A[i]-1) (A[i]1)getsum(A[i]1) 就是有多少小于 A [ i ] A[i] A[i]的数没有出现,那么必然在 A [ i ] A[i] A[i]的后边,构成逆序对,加入答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 5;
ll n, m, sum[MAXN], A[MAXN];

struct node
{
	int val, id;
	bool operator<(const node &b)const{
		return val == b.val ? id < b.id : val < b.val;
	}
} a[MAXN], b[MAXN];

ll getsum(int i)
{
	ll ans = 0;while(i > 0) ans += sum[i], i -= i & (-i);return ans;
}
void update(int i, ll k)
{
	while(i <= n) sum[i] += k, i += i & (-i); 
}

int main()
{
    //ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i].val), a[i].id = i;
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i].val), b[i].id = i;
    sort(a + 1, a + 1 + n);sort(b + 1, b + 1 + n);
    for(int i = 1; i <= n; ++i)	A[a[i].id] = b[i].id;
    ll ans = 0;
    for(int i = 1; i <= n; ++i)
    	ans += A[i] - 1 - getsum(A[i] - 1), update(A[i], 1ll), ans %= 99999997;
	printf("%lld\n", ans);
    return 0;
}

求逆序对加强版
题意:
给定序列求逆序对,有重复元素
2 s 2s 2s,开 O 2 O^2 O2 1 s 1s 1s
树状数组需要重新排序重复元素的影响,重新构造一个序列
为了消除相同元素的影响,只要所有与 a i a_i ai 相等的元素中,先出现的标记也更小就好了(我们只统计相对更大的)。具体只需要在排序时将 a i a_i ai 作为第一关键字,下标(第几个出现)作为第二关键字从小到大排序即可。
这样相同的元素中,后边于它相同的元素编号是比他大的,不会对答案产生贡献
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll c[maxn], Rank[maxn];
struct node{
	ll x, id;
	bool operator<(const  node &B)const{
		if(x != B.x) return x < B.x;
		else return id < B.id;
	}
}a[maxn];
int cnt;
int getsum(int i, ll ans = 0){
	while(i) ans += c[i], i -= i & (-i);return ans;
}
void update(int i, ll k){
	while(i <= n) c[i] += k, i += i & (-i);
}
void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> a[i].x, a[i].id = i;
	sort(a + 1, a + 1 + n);
	for(int i = 1; i <= n; ++i) Rank[a[i].id] = i;
	ll ans  = 0;
	//for(int i = 1; i <= n; ++i) cout << a[i].x << " ";cout << endl;
	//for(int i = 1; i <= n; ++i) cout << Rank[i] << " ";cout << endl;
	for(int i = 1; i <= n; ++i){
		ans += i - 1 - getsum(Rank[i] - 1);
		update(Rank[i], 1ll);
	}
	cout << ans;
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

归并排序code:
1 s 1s 1s 左右,开 O 2 O^2 O2 900 m s 900ms 900ms
不需要重新构造序列,重复元素不影响求答案,速度较快,稳定

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll a[maxn], b[maxn];
ll ms(int l, int r)
{
	if(l >= r) return 0ll;
	int mid = l + r >> 1;
	ll ans = ms(l, mid) + ms(mid + 1, r);
	int i = l, j = mid + 1, k = l;
	while(i <= mid && j <= r)
	{
		if(a[i] > a[j]){
			ans += mid - i + 1;
			b[k++] = a[j++];
		}
		else b[k++] = a[i++];
	}
    while(i <= mid) b[k++] = a[i++];
    while(j <= r) b[k++] = a[j++];
    for(int i = l; i <= r; i++) a[i] = b[i];//将排序的值返回给a 
	return ans;
}
void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> a[i];
	cout << ms(1, n);
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 逆序数是指在一个序列中,如果一个数比它后面的数小,则这两个数构成一个逆序对。以下是Python中求逆序数的一种简单算法: ```python def count_inversions(arr): """ 返回一个序列的逆序数 """ inversions = 0 for i in range(len(arr)): for j in range(i+1, len(arr)): if arr[i] > arr[j]: inversions += 1 return inversions ``` 这个算法的时间复杂度是 $O(n^2)$,如果需要处理大量的数据,可以考虑使用更高效的算法,比如归并排序。 ### 回答2: 逆序数指的是一个数的每个位上的数字按照相反的顺序排列,例如逆序数"1234"就是"4321"。而在Python中,可以使用字符串的切片来实现逆序数的表达。 首先,我们将需要逆序的数转换为字符串类型,可以使用str()函数将其转换为字符串。接下来,使用字符串的切片[::-1],这个切片的作用是按照相反的顺序取出字符串中的每个字符,达到逆序的效果。最后,将逆序后的字符串转换为整数类型,可以使用int()函数来实现。 下面是一个示例代码,演示了如何使用Python逆序数表达: ```python num = 1234 reverse_num = int(str(num)[::-1]) print(reverse_num) ``` 运行以上代码,输出结果为4321,即为数值1234的逆序数表达。 当然,在实际应用中,如果我们需要将逆序数表达应用到更复杂的问题上,可能还需要进行更多的操作和判断。但是基本的逆序数表达可以通过上述方法简单地实现。 ### 回答3: Python中可以使用切片操作来逆序输出一个数列或字符串。切片操作使用方括号和冒号来指定开始位置和结束位置,其中开始位置默认为0,结束位置默认为数列的长度。 如果要逆序输出一个数列,可以使用切片操作中的负数索引。负数索引从最后一个元素开始计数,即-1表示最后一个元素,-2表示倒数第二个元素,以此类推。通过指定开始位置为负数索引、结束位置为第一个元素之前的索引,再指定步长为-1,即可实现逆序输出数列。 举个例子,假设有一个数列nums = [1, 2, 3, 4, 5],要逆序输出这个数列,可以使用以下代码: ```python nums = [1, 2, 3, 4, 5] reverse_nums = nums[::-1] print(reverse_nums) ``` 运行这段代码,就会输出逆序后的数列[5, 4, 3, 2, 1]。 同样地,如果要逆序输出一个字符串,也可以使用切片操作。例如,有一个字符串s = "hello",要逆序输出这个字符串,可以使用以下代码: ```python s = "hello" reverse_s = s[::-1] print(reverse_s) ``` 运行这段代码,就会输出逆序后的字符串"olleh"。 总结起来,Python中逆序输出数列或字符串可以通过使用切片操作,将开始位置指定为负数索引,结束位置指定为第一个元素之前的索引,并设置步长为-1来实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值