专栏文章
P1966题解
P1966题解参与者 4已保存评论 5
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 5 条
- 当前快照
- 1 份
- 快照标识符
- @miqims9q
- 此快照首次捕获于
- 2025/12/04 05:25 3 个月前
- 此快照最后确认于
- 2025/12/04 05:25 3 个月前
题意简化
暴力简述
注意到,交换 与交换 在计算答案时是等价的。所以,直接暴力枚举即可,没有什么太难的思维难度,代码就不放了。
正解思路及部分代码片段
首先,见【暴力简述】,可以固定一个数组,交换另一个数组中的元素,尽可能的最小化 。那么我们来想一想,什么情况下这个值会最小呢?比较容易地可以想到: 序列每个元素的排名相同。即:令 为 数组排序后(此时已经交换完毕)第 个元素的下标, 为 数组排序后(此时已经交换完毕)第 个元素的下标。则 (如有相同,则可任取,只要有一种情况满足即可)。即求:把 变成 的最少交换次数模 的结果。那还是不能在 秒之内求出答案呀!通过交换相邻元素,使一个序列变成另一个序列,让我们很快就能够想到:逆序对。
在这里解释一下:若有一个长度为 的序列 ,则满足 且 的 就是一组逆序对。它的逆序对个数 的计算方式如下:
在这里解释一下:若有一个长度为 的序列 ,则满足 且 的 就是一组逆序对。它的逆序对个数 的计算方式如下:
(上述公式中 符号的意思:若里面逻辑表达式为真,则返回 ;否则,返回 )。
那么,已知一个序列,该如何求呢?暴力当然很简单,双层循环加判断,统计一下就行了。然而时间复杂度是 。但是当然可以优化,用归并排序和双指针(可能还有一些其它方法吧,但是我不会)。若需求序列 中 ~ 的子段中的逆序对个数,可作以下处理:
那么,已知一个序列,该如何求呢?暴力当然很简单,双层循环加判断,统计一下就行了。然而时间复杂度是 。但是当然可以优化,用归并排序和双指针(可能还有一些其它方法吧
其中, 表示序列 中 ~ 的子段中的逆序对个数(序列 中的逆序对个数), 表示 式中 时的结果(这里的 符号表示一段左闭右闭实数区间,但是注意: 都是整数)。即:
(上述公式中 符号的意思:若里面逻辑表达式为真,则返回 ;否则,返回 )。
然而,根据主定理,若有一函数 满足其时间复杂度计算方式如下:
然而,根据主定理,若有一函数 满足其时间复杂度计算方式如下:
(表示一个规模为 的问题被划分成 个规模为 的子问题,在其他处理时(如合并)的时间复杂度为 )
则其时间复杂度的计算方式为:
则其时间复杂度的计算方式为:
此时,,,,属于上面公式的第二种情况。(可以看作 )。没有起到十分有用的优化。我们发现,时间复杂度的瓶颈是 ,所以我们需要优化 的时间复杂度。接下来就需要用归并排序和双指针了。
如果在合并的时候(),区间 ( ~ )和区间 ( ~ )都是单调不下降的,那么在合并的时候,是不是就能优化了呢?
在合并的时候,我们需要做两件事:将两个单调不下降区间( ~ 和 ~ )合并成一个单调不下降的大区间( ~ )、统计逆序对数量( 式)。
首先,先看第一件事,将两个单调不下降的区间合并成一个单调不下降的大区间,很容易地就能够想到双指针。下面提供一个例子(片段):
CPP如果在合并的时候(),区间 ( ~ )和区间 ( ~ )都是单调不下降的,那么在合并的时候,是不是就能优化了呢?
在合并的时候,我们需要做两件事:将两个单调不下降区间( ~ 和 ~ )合并成一个单调不下降的大区间( ~ )、统计逆序对数量( 式)。
首先,先看第一件事,将两个单调不下降的区间合并成一个单调不下降的大区间,很容易地就能够想到双指针。下面提供一个例子(片段):
int p1 = l1, p2 = l2, p3 = l1;
while(p1 <= r1 && p2 <= r2) {
if(a[p1] > a[p2]) b[p3++] = a[p2++];
else b[p3++] = a[p1++];
}
while(p1 <= r1) b[p3++] = a[p1++];
while(p2 <= r2) b[p3++] = a[p2++];
for(int i = l1; i <= r2; i ++) a[i] = b[i];
上面的代码片段完成了将 序列中 ~ 和 ~ 的两个单调不下降的区间合并成一个 ~ 的单调不下降的大区间的功能。
第一件事完成了。那么,如何在这个过程中,加入第二件事:统计逆序对数量( 式)呢?
首先,因为 序列中 ~ 和 ~ 的两个区间是单调不下降的,所以可以得到:在第一个循环中,若判断语句成立,则不仅 (换一种形式写上,理解就行)和 是一对逆序对,对于所有满足 的整数 (这里的 符号表示一个左闭右闭实数区间,但是因为 是整数,所以在这里也可以理解成一个左闭右闭整数区间),都有 和 是逆序对,共 对。这样就计算出了这一部分所有的逆序对了。
实际上就是在上面的代码片段中加入一个语句,变成这样:
CPP第一件事完成了。那么,如何在这个过程中,加入第二件事:统计逆序对数量( 式)呢?
首先,因为 序列中 ~ 和 ~ 的两个区间是单调不下降的,所以可以得到:在第一个循环中,若判断语句成立,则不仅 (换一种形式写上,理解就行)和 是一对逆序对,对于所有满足 的整数 (这里的 符号表示一个左闭右闭实数区间,但是因为 是整数,所以在这里也可以理解成一个左闭右闭整数区间),都有 和 是逆序对,共 对。这样就计算出了这一部分所有的逆序对了。
实际上就是在上面的代码片段中加入一个语句,变成这样:
int p1 = l1, p2 = l2, p3 = l1;
while(p1 <= r1 && p2 <= r2) {
if(a[p1] > a[p2]) {
b[p3++] = a[p2++];
ans += (r1 + 1 - p1);
}
else b[p3++] = a[p1++];
}
while(p1 <= r1) b[p3++] = a[p1++];
while(p2 <= r2) b[p3++] = a[p2++];
for(int i = l1; i <= r2; i ++) a[i] = b[i];
只需要在这个基础上对 取模即可。
好了,就这样,我们把 的时间复杂度从 优化成了 。
再根据主定理,此时,,,,属于上面公式的第三种情况,且 。。
如果想要练习的话,可以做一做 模板题-逆序对。
好了,讲了这么多,到底该如何用进这道题目中去呢?首先,肯定要得到 数组排序后的下标,但是如何知道排序后的下标呢?有不止一种实现方式。
第一种方式:将 数组定义成机构体,如下:
CPP好了,就这样,我们把 的时间复杂度从 优化成了 。
再根据主定理,此时,,,,属于上面公式的第三种情况,且 。。
如果想要练习的话,可以做一做 模板题-逆序对。
好了,讲了这么多,到底该如何用进这道题目中去呢?首先,肯定要得到 数组排序后的下标,但是如何知道排序后的下标呢?有不止一种实现方式。
第一种方式:将 数组定义成机构体,如下:
struct node {
int Val, Index;
}a[100010], b[100010];
然后再用一个函数作为排序(快速排序)函数的第三个参数:
CPPbool cmp(node u, node v) {
return (u.Val < v.Val);
}
用的时候就这么用:
CPPsort(a + 1, a + n + 1, cmp);
sort(b + 1, b + n + 1, cmp);
还有第二种方式:用 数组对 数组进行排序。
定义很普通:
CPP定义很普通:
int a[100010], b[100010], p[100010], q[100010];
记得初始化:
CPPfor(int i = 1; i <= n; i ++) {
p[i] = q[i] = i;
}
函数这么写(两个):
CPPbool cmpa(int u, int v) {
return (a[u] < a[v]);
}
bool cmpb(int u, int v) {
return (b[u] < b[v]);
}
用的时候就这么用:
CPPsort(p + 1, p + n + 1, cmpa);
sort(q + 1, q + n + 1, cmpb);
反正不管怎么样,排完序之后(离散化)都会得到两个下标数组(
第一种方式:
CPPa[i].Index 和 b[i].Index、p[i] 和 q[i])。接下来,再定义一个新数组 ,用下面的方法来处理:第一种方式:
for(int i = 1; i <= n; i ++) {
c[a[i].Index] = b[i].Index;
// c[b[i].Index] = a[i].Index;
}
第二种方式:
CPPfor(int i = 1; i <= n; i ++) {
c[p[i]] = q[i];
// c[q[i]] = p[i];
}
反正不管怎么样,现在都会得到一个 数组,答案就是对 数组计算逆序对个数然后对 取模(将 数组变成 的形式所要用的最少步数对 取模)。
就这样,这道看似很难的题就被解决了。
就这样,这道看似很难的题就被解决了。
代码
最后再补充一点,再之前讲合并函数的时候,形参一共有四个。但是不难发现,实际上并不需要四个,可以只有三个甚至两个,我个人习惯用三个形参的方式来实现,这并不代表其它方式就不行了,仅限个人习惯。好了,上代码。
第一种实现方式:
CPP第一种实现方式:
#include <bits/stdc++.h>
using namespace std;
const int Mod = 99999997;
int n, ans, c[100010], d[100010];
struct node {
int Val, Index;
}a[100010], b[100010];
bool cmp(node u, node v) {
return (u.Val < v.Val);
}
void Merge(int l, int mid, int r) {
int p1 = l, p2 = mid + 1, p3 = l;
while(p1 <= mid && p2 <= r) {
if(c[p1] > c[p2]) {
d[p3++] = c[p2++];
ans = (ans + mid + 1 - p1) % Mod;
}
else d[p3++] = c[p1++];
}
while(p1 <= mid) d[p3++] = c[p1++];
while(p2 <= r) d[p3++] = c[p2++];
for(int i = l; i <= r; i ++) {
c[i] = d[i];
}
}
void Sort(int l, int r) {
if(l == r) return;
int mid = (l + r) >> 1;
Sort(l, mid);
Sort(mid + 1, r);
Merge(l, mid, r);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i].Val);
a[i].Index = i;
}
for(int i = 1; i <= n; i ++) {
scanf("%d", &b[i].Val);
b[i].Index = i;
}
sort(a + 1, a + n + 1, cmp);
sort(b + 1, b + n + 1, cmp);
for(int i = 1; i <= n; i ++) {
c[a[i].Index] = b[i].Index;
}
Sort(1, n);
printf("%d", ans);
return 0;
}
第二种实现方式:
CPP#include <bits/stdc++.h>
using namespace std;
const int Mod = 99999997;
int n, ans, a[100010], b[100010], c[100010], d[100010], p[100010], q[100010];
bool cmpa(int u, int v) {
return (a[u] < a[v]);
}
bool cmpb(int u, int v) {
return (b[u] < b[v]);
}
void Merge(int l, int mid, int r) {
int p1 = l, p2 = mid + 1, p3 = l;
while(p1 <= mid && p2 <= r) {
if(c[p1] > c[p2]) {
d[p3++] = c[p2++];
ans = (ans + mid + 1 - p1) % Mod;
}
else d[p3++] = c[p1++];
}
while(p1 <= mid) d[p3++] = c[p1++];
while(p2 <= r) d[p3++] = c[p2++];
for(int i = l; i <= r; i ++) {
c[i] = d[i];
}
}
void Sort(int l, int r) {
if(l == r) return;
int mid = (l + r) >> 1;
Sort(l, mid);
Sort(mid + 1, r);
Merge(l, mid, r);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
p[i] = i;
}
for(int i = 1; i <= n; i ++) {
scanf("%d", &b[i]);
q[i] = i;
}
sort(p + 1, p + n + 1, cmpa);
sort(q + 1, q + n + 1, cmpb);
for(int i = 1; i <= n; i ++) {
c[p[i]] = q[i];
}
Sort(1, n);
printf("%d", ans);
return 0;
}
数据通过情况
最后的话
一定一定不要 Copy 代码!!! 并且最好不要边看边写。应该先理解每一步在做什么,对代码有了一个清晰的认识,在自己手打一遍,加深印象。本文中的代码仅供参考,不喜勿喷。
最后的最后,写篇文章不容易,可否来个赞???
最后的最后,写篇文章不容易,可否来个赞???
相关推荐
评论
共 5 条评论,欢迎与作者交流。
正在加载评论...