最近在拼题作业上再次遇到了排列问题,观察题之后发现和前一阵学的暴力枚举是一类题型。于是我通过在洛谷上学过的知识,进一步简单地解决了这个问题。
首先看一下最简单的一个排列问题(洛谷p1706),题目要求输出自然数1-n所有不重复的排列,即n的全排列。例如给出n=3,那么我们就要输出123,132,213,231,312,321。这是个非常典型的暴力枚举问题。在第一次遇到这个题目时,我简单地以为利用多个for循环就可以解决,然而实际上先不考虑时间复杂度不说,题目中连给出n是多少都是未知的,假如提前写好for循环的代码,也要提前做很多分支出来,十分复杂。
后来我在洛谷参考书中学到了这个一个函数:next_permutation(start,end),它的作用十分明显,它可以在表示[start,end)内存的数组中产生严格的下一个字典序排列。例如231下一次就变成312,之后又是321。利用这个函数强大的功能,我们可以轻松解决这类问题。以下是书中给出的参考代码:
#include "stdio.h"
#include "iostream"
#include "algorithm"//next_permutation的头文件,不要忘了加
using namespace std;
int a[10], n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
a[i] = i;
do {
for (int i = 1; i <= n; i++)
printf("%5d",a[i]); //%5d是题目要求的格式
puts("");
} while (next_permutation(a + 1, a + n + 1));//注意函数作用范围是左闭右开的
return 0;
}
可以看到,利用这个函数,可以大大减少代码的时间和空间复杂度,也利于我们解决更多相关的问题。
同理,利用这个函数,我轻松解决了洛谷中一开始并不会写的一个题目“火星人”:
第一次我根本看不懂这个题目,后来我联想到这个处理字典序非常好用的函数之后也明白了这个题目的解决方法。题目的要求相当于让我们输出给出数组之后的第M个字典序排列,因此只要利用好这个函数,我们就可以解决这个问题。
以下是解决代码:
#include "iostream"
#include "algorithm"
using namespace std;
#define N 100100
int main()
{
int n, m;
int a[N];
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
while (m--)
{
next_permutation(a + 1, a + n + 1);
}
for (int i = 1; i <= n; i++)
cout << a[i] << " ";
return 0;
}
我们只需要规定好函数作用范围是从a[1]到a[n+1],因为这个函数作用范围左闭右开,因此我们让数组从1开始,直到n+1这个位置。同时这个题目也没给出n的具体值,这样处理不需要我们提前考虑分支等等。而之前做过的烤鸡这个题目(洛谷p2089)因为我们已知枚举的数字只有10个,所以我们可以直接利用多个for循环完成。
同时,这个函数还可以用于三连击加强版(洛谷p1618)这个题目中(要提醒的是,这个题目在判断三个数比例是A:B:C时,不要让三个数相比等于ABC,可以利用交叉相乘的方式进行判断,我一开始就是用的相比的方法,没做出来,,ԾㅂԾ,,),使用这个函数帮助我们枚举将会非常方便。
这就是next_permutation这个十分方便的函数的应用,非常便利且简洁(给我这个蒟蒻极大的帮助和心理安慰)。