【排列枚举】next_permutation的应用


排列枚举就是枚举所有元素的排列情况,而对于此类题目大多数都可以用 next_permutation函数生成各个元素的不同排列解决,因此本文重在学习使用这个函数解决问题。

用法剖析

next_permutation(start,end)algorithm标准库函数,它可以重新排列[start,end)范围内的数组并产生严格的下一个字典序排列。如果有字典序更高的排列方式,就按这种方式排列并返回为true,否则就返回false并重新将排列变成字典序最小的方式。
简单验证一下

#include<iostream>
#include<algorithm> 
using namespace std;
int a[10];
int main()
{
	for(int i=1;i<=5;i++)
	{
		a[i]=i;
	}	
	do
	{
		for(int i=1;i<=5;i++)
		{
			cout<<a[i]<<' ';
		}
		cout<<endl;
	}
	while(next_permutation(a+1,a+6));
	return 0;
}

通过这几行代码我们可以按字典序输出从1到5的全排列(太长了,中间省略QAQ):

1 2 3 4 5
1 2 3 5 4
1 2 4 3 5
1 2 4 5 3
……
1 5 4 3 2
2 1 3 4 5
……

5 4 3 1 2
5 4 3 2 1

可以看到,在输出字典序最大的排列后,程序就停了下来。现在我们修改代码使程序死循环,看看字典序最大时结果会如何变化。
我们将中间几行代码改为:

	while(1)
	{
		next_permutation(a+1,a+6);
		for(int i=1;i<=5;i++)
		{
			cout<<a[i]<<' ';
		}
		cout<<endl;
	}

看一下运行结果:

1 2 3 5 4
1 2 4 3 5
1 2 4 5 3
……
5 4 3 1 2
5 4 3 2 1
1 2 3 4 5
1 2 3 5 4
……
5 4 3 2 1
1 2 3 4 5
……
5 4 3 2 1
1 2 3 4 5
……
5 4 3 2 1
1 2 3 4 5
……
5 4 3 2 1
1 2 3 4 5
……

将两次运行结果对比,我们可以发现当next_permutation函数返回false,也就是字典序达到最大值时,函数就会将其变成字典序最小的排列形式,与前面所说一致。
枚举全排列的时间复杂度是O(n!),一般不能超过11个元素。

实战练习

例题1:全排列问题

按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字,每个数字保留5个场宽。

这道题是深搜的模板题,如果不知道next_permutation的话,就只能傻傻的用递归写:

#include<iostream>
using namespace std;
const int N=10001;
int path[N],book[N];
int n;
void dfs(int u)
{
	if(u==n+1)
	{
		for(int i=1;i<=n;i++)
			printf("%5d",path[i]);
		cout<<endl;
		return;
	}
	for(int i=1;i<=n;i++)
	{
		if(!book[i])
		{
			path[u]=i;
			book[i]=1;
			dfs(u+1);
			book[i]=0;
		}				
	}
	
}
int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

当初理解不了回溯,被虐的死去活来,而现在借助next_permutation,我们只需要简单的循环就能解决问题:

#include<iostream>
#include<algorithm> 
using namespace std;
const int N=1e5;
int a[N];
int 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]);
		}
		cout<<endl;
	}
	while(next_permutation(a+1,a+n+1));
	return 0;
}


怎么样,感受到它的威力了吗?我们趁热打铁,看一下第二道例题:

例题2:火星人

人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。
火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为1,2,3⋯。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。
一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 1,2,3,4 和5,当它们按正常顺序排列时,形成了 5 位数 12345,当你交换无名指和小指的位置时,会形成 5 位数12354,当你把五个手指的顺序完全颠倒时,会形成 54321,在所有能够形成的 120 个 5 位数中,12345 最小,它表示 1;12354 第二小,它表示 2;54321 最大,它表示 120。
现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。

是不是有点晕?这道题歪歪斜斜的每行都写着”火星人“三个字,我横竖睡不着,仔细看了半夜,才从字缝里看出字来,满本都写着两个字是”排列“!(手动滑稽)
用一句话概括题意,其实就是求从1到n的字典序全排列的第m个排列,直接用next_permutation即可:

#include<iostream>
#include<algorithm> 
using namespace std;
const int N=1e5;
int a[N];
int n,m;
int main()
{
	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]<<' ';
	}
	cout<<endl;
	return 0;
}


  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值