本文为武汉大学郑未《蓝桥杯历届真题解析》学习笔记。
添加链接描述
(第三题)
有如下十个方格:
**要求:**连续的两个数不能相邻。
(左右、上下、对角都算相邻)
求有多少种填法。
**考点:**全排列
解法一:
使用递归来对0-9这10个数进行全排列,再检查排列的结果是否符合题目要求。
/*方格填数*/
#include
#include
using namespace std;
int a[10]={0,1,2,3,4,5,6,7,8,9};
int ans=0;
int main(int argc,const char *argv[])
{
void f(int k);
f(0);
cout<<ans<<endl;
return 0;
}
bool check()
{
if(abs(a[0]-a[1])==1 ||
abs(a[0]-a[3])==1 ||
abs(a[0]-a[4])==1 ||
abs(a[0]-a[5])==1 ||
abs(a[1]-a[2])==1 ||
abs(a[1]-a[4])==1 ||
abs(a[1]-a[5])==1 ||
abs(a[1]-a[6])==1 ||
abs(a[2]-a[5])==1 ||
abs(a[2]-a[6])==1 ||
abs(a[3]-a[4])==1 ||
abs(a[3]-a[7])==1 ||
abs(a[3]-a[8])==1 ||
abs(a[4]-a[5])==1 ||
abs(a[4]-a[7])==1 ||
abs(a[4]-a[8])==1 ||
abs(a[4]-a[9])==1 ||
abs(a[5]-a[6])==1 ||
abs(a[5]-a[8])==1 ||
abs(a[5]-a[9])==1 ||
abs(a[6]-a[9])==1 ||
abs(a[7]-a[8])==1 ||
abs(a[8]-a[9])==1)
return false;
else
return true;
}
/*用递归来进行全排列*/
/*考虑第k个位置,一般从0开始*/
void f(int k)
{
if(k==10){
bool b=check();
if(b)
ans++;
return;
}
for(int i=k;i<10;++i){
/*尝试将位置i与位置k交换,以此确定k位的值*/
{
int t=a[i];
a[i]=a[k];
a[k]=t;
}
f(k+1);
/*回溯*/
{
int t=a[i];
a[i]=a[k];
a[k]=t;
}
}
}
解法二:
用一个如下图所示的5*6的二维数组中间部分的空间来存储0-9的全排列,数组中的其他元素填-10(如图中填-1的那些单元格),这样对所有0-9的元素,都可用一个双重的for循环来检测其处于中心位置的九宫格中,是否存在数值与它是相连的。
#include
#include
using namespace std;
int a[5][6];
int vis[10];
int ans;
int main()
{
void init();
void f(int x,int y);
init();
f(1,2);
cout<<ans<<endl;
return 0;
}
/*检查是否合法*/
bool check(int i,int j)
{
int x,y;
for(x=i-1;x<i+2;x++)
for(y=j-1;y<j+2;y++)
if(abs(a[i][j]-a[x][y])==1)
return false;
return true;
}
/*进行全排列*/
void f(int x,int y)
{
if(x==3 && y==4){
ans++;
return;
}
int i;
for(i=0;i<10;i++){
if(vis[i]==0){ //没有被用过
a[x][y]=i; //填数
if(!check(x,y)){ //检查(在过程中即进行)不合法,恢复并continue
a[x][y]=-10;
continue;
}
vis[i]=1; //标记为已访问
/*递归*/
if(y==4)
f(x+1,1); //如果y值已到边界,换行
else
f(x,y+1); //继续填右侧的格子
{vis[i]=0; //回溯
a[x][y]=-10;}
}
}
}
/*初始化*/
void init()
{
int i,j;
for(i=0;i<5;i++)
for(j=0;j<6;j++)
a[i][j]=-10;
}
解法三:
调用头文件中的next_permutation()函数来进行全排列操作,沿用解法二的check()操作。
/*方格填数*/
#include
#include
#include
#include
#include
using namespace std;
int a[10]={0,1,2,3,4,5,6,7,8,9};
int b[5][6];
int ans=0;
int main()
{
bool check(int i,int j);
void init();
//memset(b,-1,sizeof(b)); //为何不能初始化为-2,-10等
int i,j,k,Count=0;
bool flag=true;
for(i=0;i<5;i++) //对 b[][]中所有的元素进行初始化
for(j=0;j<6;j++)
b[i][j]=-10;
while(next_permutation(a,a+10)){ //调用next_permutation函数对0-9进行全排列
i=1;
j=2;
for(k=0;k<10;k++){
b[i][j]=a[k]; //将全排列后的元素填入b[][]中特定空间
if(!check(i,j)){
init(); //此时,一定要将已填入值的元素初始化为-10
flag=false;
break;
}
j++;
if(j==5){
i++;
j=1;
}
}
if(flag)
Count++;
else
flag=true;
}
cout<<Count<<endl;
return 0;
}
/*对已填入数的元素进行初始化*/
void init()
{
int x,y;
for(x=1;x<=3;x++)
for(y=1;y<=4;y++)
b[x][y]=-10;
}
/*检查新填入的数据是否满足题目需求*/
bool check(int i,int j)
{
int x,y;
for(x=i-1;x<i+2;x++){
for(y=j-1;y<j+2;y++)
if(abs(b[x][y]-b[i][j])==1)
return false;
}
return true;
}
注意点:
(1)每次check()数据不合格后,需要对所有已填入0-9的数组元素进行初始化,否则下次check()时,上次填入的元素会影响判断。我最开始没想到这个问题,最后输出的ans值为0。
(2)用next_permutation()函数对数组元素进行全排列时,需先将该数组中的元素按从小到大的顺序排好序,因为该函数默认当数组中元素降序排序时,全排列操作完成,退出执行。
next_permutation()用法示例:
#include
#include
using namespace std;
int a[3]={0,1,2};
int main()
{
int i;
while(next_permutation(a,(a+3))){
for(i=0;i<3;i++)
cout<<a[i]<<' ';
cout<<endl;
}
return 0;
}
(第六题)
现在小学的数学题目也不是那么好玩的。
看看这个寒假作业:
□ + □ = □
□ - □ = □
□ × □ = □
□ ÷ □ = □
每个方块代表1~13中的某一个数字,但不能重复。
比如:
6 + 7 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
以及:
7 + 6 = 13
9 - 8 = 1
3 * 4 = 12
10 / 2 = 5
就算两种解法。(加法,乘法交换律后算不同的方案)
请问:你一共找到了多少种方案?
考点:对1-13个数进行全排列
解法一:
用递归进行全排列
#include
#include
using namespace std;
int a[13]={1,2,3,4,5,6,7,8,9,10,11,12,13};
int ans=0;
bool check()
{
if(a[0]+a[1]==a[2] &&
a[3]-a[4]==a[5] &&
a[6]*a[7]==a[8] &&
a[9]%a[10]==0 &&
a[9]/a[10]==a[11])
return true;
return false;
}
/*用递归实现全排列*/
void f(int k)
{
if(k==13){
if(check())
ans++;
}
int i,t;
for(i=k;i<13;++i){
{t=a[i];a[i]=a[k];a[k]=t;} //交换
f(k+1);
{t=a[i];a[i]=a[k];a[k]=t;} //回溯
}
}
int main()
{
f(0);
cout<<ans<<endl;
return 0;
}
由于是对13个数进行全排列,程序运行较慢:
可增加一些检验条件,及时终止部分不合要求的排列,可对f函数稍作更改:
/*用递归实现全排列*/
void f(int k)
{
if(k==13){
if(check())
ans++;
}
int i,t;
for(i=k;i<13;++i){
{t=a[i];a[i]=a[k];a[k]=t;} //交换
if((k==2 && a[0]+a[1]!=a[2]) || (k==5 && a[3]-a[4]!=a[5]) ||(k==8 && a[6]*a[7]!=a[8])){
{t=a[i];a[i]=a[k];a[k]=t;} //如不合要求,撤销前一步的交换操作
continue;
}
f(k+1);
{t=a[i];a[i]=a[k];a[k]=t;} //回溯
}
}
另外,在这道题上我尝试用next_permutation()算法来进行全排列,运行结果很慢很慢。
又分析了上面next_permutation()进行全排列的规律(如下图),来增加适当的检验条件,程序没运行出来。可能是我的程序本身有问题,暂时将这个失败的程序记在这里。
#include
#include
using namespace std;
int a[13]={1,2,3,4,5,6,7,8,9,10,11,12,13};
int main()
{
int i,j,k,Count=0;
while(next_permutation(a,(a+13))){
/*如果a[0]==k排列不合题目要求,则开始a[0]==k+1的新一轮的排列*/
if((a[0]+a[1]!=a[2]) || (a[3]-a[4]!=a[5]) ||(a[6]*a[7]!=a[8])){
k=a[0];
k++;
a[0]=k;
j=1;
for(i=1;i<13;i++){
if(i!=k)
a[j++]=i;
}
}
if(a[2]==a[0]+a[1] && a[5]==a[3]-a[4] && a[8]==a[6]*a[7] && a[8]%a[9]==0 && a[11]==a[8]/a[9])
Count++;
}
cout<<Count<<endl;
return 0;
}
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,下面两张图中,粉红色所示部分就是合格的剪取。
请你计算,一共有多少种不同的剪取方法。
此题与13年剪格子有相似之处,但那个题的限制条件是格子数值之和为总和的一半,此题限制只能是5个格子。
注意点:
单纯的dfs()无法解决T字型连通方案(不能同时往水平和垂直两个方向走)。
本题的解决方法是,找出任意5个格子,判断是否连通。
(1)从12个格子里面选出5个(全排列,但是是对重复的元素进行全排列)。
(2)在二维数组中检测连通性(经典问题),可用dfs快速解决。
解法一:
在对12个元素进行全排列后再检测其是否符合题目要求,运行较慢,运行时间为1300多秒。
/*剪格子:在排列之后再做重复性判断*/
#include
#include
#include
using namespace std;
/*它的每一个排列代表12选5的一个方案,排好后将1所在的下标转为二维数组的下标,再检测连通性*/
int a[]={0,0,0,0,0,0,0,1,1,1,1,1};
int ans=0;
void dfs(int g[3][4],int i,int j)
{
g[i][j]=0;
if(i-1>=0 && g[i-1][j]==1) dfs(g,i-1,j);
if(i+1<=3 && g[i+1][j]==1) dfs(g,i+1,j);
if(j-1>=0 && g[i][j-1]==1) dfs(g,i,j-1);
if(j+1<=4 && g[i][j+1]==1) dfs(g,i,j+1);
}
/*连通性检测*/
bool check()
{
int g[3][4]; //代表图
int i,j;
/*将某个排列映射到二维矩阵上*/
for(i=0;i<3;++i){
for(j=0;j<4;++j){
if(a[i*4+j]==1)
g[i][j]=1;
else
g[i][j]=0;
}
}
int cnt=0; //连通块数目
/*g中有5个格子被标记为1后,用dfs做连通性检验,要求只有一个连通块*/
for(i=0;i<3;++i)
for(j=0;j<4;++j){
if(g[i][j]==1){
dfs(g,i,j);
cnt++;
}
}
return(cnt==1);
}
set<string> s1; //引入set,将数组变成字符串,去除重复元素
void a2s(string &s)
{
int i;
for(i=0;i<12;++i){
s.insert(s.end(),a[i]+'0');
}
}
/*因为是对12个0或1进行全排列,有大量的重复结果,所以需要检验这样的结果之前是否存在过*/
bool isExist()
{
string a_str;
a2s(a_str);
/*如果没找着a_str,就将其存在s1中,并返回false*/
if(s1.find(a_str)==s1.end()){
s1.insert(a_str);
return false;
}
else
return true;
}
void f(int k)
{
int i,t;
if(!isExist() && check()==12){
ans++;
}
for(i=k;i<12;i++){
{t=a[i];a[i]=a[k];a[k]=t;}
f(k+1);
{t=a[i];a[i]=a[k];a[k]=t;}
}
}
int main()
{
f(0);
cout<<ans<<endl;
/*string s;
a2s(s);
cout<
return 0;
}
下图是对代码中涉及的深度遍历的一个图示:
从一个非0的格子起,对它进行dfs,如果能走通(有相连为1者),将该格子置为0(标记访问过)
解法二:
抓取法
从12个元素中去抓取5个来构成path,是全排列的另一种方案,运行结果较快。
#include
using namespace std;
int a[]={0,0,0,0,0,0,0,1,1,1,1,1};
int ans=0;
void dfs(int g[3][4],int i,int j)
{
g[i][j]=0;
if(i-1>=0 && g[i-1][j]==1) dfs(g,i-1,j);
if(i+1<=2 && g[i+1][j]==1) dfs(g,i+1,j);
if(j-1>=0 && g[i][j-1]==1) dfs(g,i,j-1);
if(j+1<=3 && g[i][j+1]==1) dfs(g,i,j+1);
}
/*连通性检测*/
bool check(int path[12])
{
int g[3][4]; //代表图
int i,j;
/*将某个排列映射到二维矩阵上*/
for(i=0;i<3;++i){
for(j=0;j<4;++j){
if(path[i*4+j]==1)
g[i][j]=1;
else
g[i][j]=0;
}
}
int cnt=0; //连通块数目
/*g中有5个格子被标记为1后,用dfs做连通性检验,要求只有一个连通块*/
for(i=0;i<3;++i)
for(j=0;j<4;++j){
if(g[i][j]==1){
dfs(g,i,j);
cnt++;
}
}
return(cnt==1);
}
bool vis[12];
void f(int k,int path[12])
{
int i;
if(k==12){
if(check(path))
ans++;
}
for(i=0;i<12;++i){
/*现在准备抓取的元素和上一个元素相同,但上一个元素还没有被使用,跳过,以避免重复*/
if(i>0 && a[i]==a[i-1] && !vis[i-1])
continue;
if(!vis[i]){ //没有被用过的元素可以抓入到path
vis[i]=true; //标记为已访问
path[k]=a[i]; //填入到path
f(k+1,path); //递归
vis[i]=false; //回溯
}
}
}
int main()
{
int path[12];
f(0,path);
cout<<ans<<endl;
return 0;
}
解法三:
使用next_permutation()进行全排列。
#include
#include
using namespace std;
int ans;
void dfs(int g[3][4],int i,int j)
{
g[i][j]=0;
if(i-1>=0 && g[i-1][j]==1) dfs(g,i-1,j);
if(i+1<=2 && g[i+1][j]==1) dfs(g,i+1,j);
if(j-1>=0 && g[i][j-1]==1) dfs(g,i,j-1);
if(j+1<=3 && g[i][j+1]==1) dfs(g,i,j+1);
}
/*连通性检测*/
bool check(int path[12])
{
int g[3][4]; //代表图
int i,j;
/*将某个排列映射到二维矩阵上*/
for(i=0;i<3;++i){
for(j=0;j<4;++j){
if(path[i*4+j]==1)
g[i][j]=1;
else
g[i][j]=0;
}
}
int cnt=0; //连通块数目
/*g中有5个格子被标记为1后,用dfs做连通性检验,要求只有一个连通块*/
for(i=0;i<3;++i)
for(j=0;j<4;++j){
if(g[i][j]==1){
dfs(g,i,j);
cnt++;
}
}
return(cnt==1);
}
int main()
{
int pre[]={0,0,0,0,0,0,0,1,1,1,1,1};
do{
if(check(pre))
ans++;
}while(next_permutation(pre,(pre+12)));
cout<<ans<<endl;
return 0;
}