NOIP 2012 普及组初赛 第28题 排列数

【题目】

NOIP 2012 普及组初赛 第28题 排列数
完善程序 (排列数)
输入两个正整数 n,m(1<n<20,1<m<n),在 1∼n 中任取 m 个数,按字典序从小到大输出所有这样的排列。
例如: 输入:3 2
输出:
1 2
1 3
2 1
2 3
3 1
3 2

#include <iostream>
#include <cstring>
using namespace std;
const int SIZE =25;
bool used[SIZE];
int data[SIZE];
int n,m,i,j,k;
bool flag;
int main()
{
    
    
	cin>>n>>m;
	memset(used,false,sizeof(used));
	for(i=1;i<=m;i++)
	{
    
    
		data[i]=i;
		used[i]=true;	
	}
	flag=true;
	while(flag)
	{
    
    
		for(i=1;i<=m-1;i++) cout<<data[i]<<" ";
		cout<<data[m]<<endl;
		flag= [] ;
		for(i=m;i>=1;i--)
		{
    
    
			[];
			for(j=data[i]+1;j<=n;j++)
				if(!used[j])
				{
    
    
					used[j]=true;
					data[i]=[] ;
					flag=true;
					break;	
				}
			if(flag)
			{
    
    
				for(k=i+1;k<=m;k++)
					for(j=1;j<= [];j++)
					if(!used[j])
					{
    
    
						data[k]=j;
						used[j]=true;
						break;
					}
				[];
			}
		}
	}
	return 0;	
}

【题目考点】

1. 循环

2. 标志位

【解题思路】

求排列数,我们学过用深搜解决,但看该题的解法不像深搜。我们需要一点一点搞清该代码所用的算法。

cin>>n>>m;
memset(used,false,sizeof(used));
for(i=1;i<=m;i++)
{
    
    
	data[i]=i;
	used[i]=true;	
}

先输入n与m,要在1~n中输出m个数字的排列。
看变量名,就能猜出这个变量名的意思,data[i]是第i个数据,used[i]意思就应该是第i个数是否已经使用过。初值是使用过。
flag是什么意思还待定。
只要flag为真,就进行while循环,进入while循环内部

for(i=1;i<=m-1;i++) cout<<data[i]<<" ";
cout<<data[m]<<endl;
flag= [] ;

先是输出是data下标1~m,这表明data保存的是要输出的数字排列。输出的第一个排列就是1,2,…,m。
然后重新给flag设值。能进入while循环,flag一定是true。而这里要为flag重新设值,那一定是设为false。所以①填false

for(i=m;i>=1;i--)
{
    
    
	[];
	for(j=data[i]+1;j<=n;j++)
		if(!used[j])
		{
    
    
			used[j]=true;
			data[i]=[] ;
			flag=true;
			break;	
		}

然后i从m循环到1,②处做了一些事情,先向下看。j从data[i]+1循环到n,如果j没有用过,就把j设为用过。然后给i的位置重新设值。
看到这里,大概就能看出题目代码要做的事情。data数组保存一个排列,而该段代码就是让data某一位变大,使其变为字典序下的下一个排列。

假设n是5,m是3,在1~5中选3个数字做排列,先是123,而后改变最后一位变为124,接着是125

因此此时要改变的就是data[i]这个数,需要将data[i]原有的值设为“没用过”,②处应该填used[data[i]] = false
然后从data[i]+1开始,找一个没有用过的数字j,作为data[i]的新的值。所以③处应该填j
可见flag表示是否有某位置的值发生改变。如果某位置的值改变了,flag变为true,会运行进入下一段。否则不进入下一段,i--,进入下一次循环,改变data中i前一个位置的值。

if(flag)
{
    
    
	for(k=i+1;k<=m;k++)
		for(j=1;j<= [];j++)
		if(!used[j])
		{
    
    
			data[k]=j;
			used[j]=true;
			break;
		}
	[];
}

此时考虑,如果第i位的值已经改变了,接下来就需要将第i+1到最后一位的值变为可能的字典序最小的序列。

在1~5中选3个数字做排列,已经数到145,接着改变倒数第二位,把4变为5,而后最后一位需要变为字典序最小的且没用过的2,所以下一个排列为152,然后153,154。
接下来需要改变第1位,把第1位变为2,而后需要把后两位变为字典序最小的13,因此下一个排列为213

从第i+1位置开始,为每个位置都赋予当前没用过的数字中的最小值。
j从1开始,最大可以取到n,④位置填n
⑤位置处于对i的循环之内,对k、j的循环之外。如果进入了if(flag)下的代码块,说明某位置的值改变了,并且整个data数组已经变为字典序下的下一个排列了。
那么接下来应该再次从末尾开始向前取找一个值可以变化的位置。因此应该跳出当前对i的循环,在更外层while(flag)的作用下,重新进度对i的循环,从末尾开始向前找一个位置,改变这个位置的值。因此⑤处填break
如果已经找到了最后一个排列,那么不会有任何位置的值被改变,flag就是false,会跳出while循环。

在1~5中选3个数字做排列,如果排列已经是543,整段代码运行下来,flag都不会为true。

【答案】

① false
② used[data[i]]=false
③ j
④ n
⑤ break

猜你喜欢

转载自blog.csdn.net/lq1990717/article/details/126175236