AT ABC-276th C 题解
题意理解
算是个小水的题,做题的时候耗了好久才理解题意,可见英语是很重要的
首先,题意中说明存在一个排列\(A\),满足排列长度为\(n\),且所含元素一定是区间\([1,n]\)中的。给出\(A\)的另一种排列方式,这种排列方式暂且称之为\(B\),\(B\)满足\(A\)所满足的所有条件,且在全排列中是最小的比\(A\)字典序大的排列。
那么经过转换,我们进一步可理解为\(A\)、\(B\)都是\(\{1,2,……,n\}\)的全排列中的一个序列,且\(A\)是最大的比\(B\)小的序列(从字典序的意义上说)。
OK,题意理解完毕,下一步!
解题方法
我们知道全排列的任意一个序列中是没有重复元素的,所以只能是以交换的方式来得到下一个字典序比当前序列大的序列,\(A、B\)这两个序列也一定是通过交换的方式得到的。
依题意,\(A\)的字典序仅次于\(B\),而一个序列进行元素交换得到一个新的字典序比原序列小的序列的必要条件就是处理最靠后的非升序部分,所以就有一个贪心的策略:尽量从序列后面进行交换,也就是倒序查找逆序对。
倒序查找到第一个逆序对\((x,y) \ x<y\),此时在整个序列中\(x\)前面的部分是不需要变的,只要改动\(y\)后面的部分即可(至于为什么请结合前面讲的自行理解)。
举个例子来说,给出\(B\)序列为\(\{3,1,2\}\),此时最后的逆序对是\((3,1)\),但交换后的序列并不能满足题目要求,满足题目要求的是\(\{2,3,1\}\)
此时我们就可以发现,处理的过程是把\(3\)和\(2\)交换,再倒序输出\(2\)以后的部分。
所以从找规律的角度来说,这题已经可以做了,不想深入的童鞋可以去取代码了。
本质原理:
-
某个数对如果是降序,就需要把后面的部分重构,因为实际上要修改的是降序后面的升序,一旦数对成为升序,那么就代表这个数对不能再进行交换了,只能去前面找帮助,求最后的逆序对本质意义是求序列末尾最长升序子串的长度。
-
找到末尾最长升序子串后,就要进行重构了。首先,把最后的逆序对的前一个元素称为\(x\),将后面的元素一一与\(x\)比较,找到最大的比\(x\)小的值,再将这个新的\(max\)与原先的\(x\)互换。这一步的意义在于,想要让字典序最小限度变小,那么\(x\)就应当最小限度减小,所以要最大化\(max\),\(x\)到了\(max\)的位置是为下一步倒序输出做准备。
3.到了最后一步,也就是倒序输出。
为什么要倒序输出呢?还是中心思想:最大限度的减少变小的幅度。原来这个部分是升序的,现在前面的\(x\)减小了,那后面就可以变大了,两两互换,得到一个倒序序列,就实现了我们的目标。
代码:
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
#define ll long long
const int N = 105;
int a[N];
int main()
{
int n, i, id;
cin >> n;
for (i = 0; i < n; i++)
{
cin >> a[i];
}
for (i = n - 1; i >= 1; i--)
{
if (a[i] < a[i - 1])
{
id = i;
break;
}
}
id--;
int max_ = -1, flag = 0;
for (i = n - 1; i >= id; i--)
{
if (a[i] < a[id] && a[i] > max_)
{
max_ = a[i];
flag = i;
}
}
// cout << id << ' ' << flag << endl;
swap(a[id], a[flag]);
for (i = 0; i <= id; i++)
{
cout << a[i] << ' ';
}
for (i = n - 1; i > id; i--)
{
cout << a[i] << ' ';
}
return 0;
}
马蜂丑陋勿喷
感谢阅读

浙公网安备 33010602011771号