蓝桥杯及其搜索算法总结

        蓝桥杯,考暴力和搜索,这是众所周知的事情,近几年的题目非常非常的多。

        搜索的基本理论:

                   1、回溯法:当把问题分成若干个步骤并递归求解时,如果当前步骤没有合法选择,则函数将返回上一级递归调用,这种现象就称回溯。

                   2、路径寻找问题:路径寻找问题可以归结为隐式图的遍历,它的任务是找到一条从初始状态到终止状态的最优路径,而不是像回溯法那样找到一个符合某些要求的解。

                      常见的两种方法是:深度优先搜索,广度优先搜索。

      具体例题分析:

      1、模型一:

2016第三题

凑算式
       B      DEF
A + — + -——— = 10
       C       GHI
(如果显示有问题,可以参见【图1.jpg】)
这个算式中A~I代表1~9的数字,不同的字母代表不同的数字。
比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。
这个算式一共有多少种解法?
注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。

#include

using namespace std;
int b[10];
int visited[10]={0};
void dfs(int k,int i);
void sovle();
int sum=0;
int main()
{
	int i;
	for(i=1;i<=9;i++)
	{
		dfs(1,i);
		visited[i]=0;
	}
	printf("%d\n",sum);
	return 0;
} 

void dfs(int k,int i)
{
	int j;
	visited[i]=1;
	b[k]=i;
	if(k==9)
	{
		sovle();
	}
	else
	{
		for(j=1;j<=9;j++)
		{
			if(visited[j]==0)
			{
				dfs(k+1,j);
				visited[j]=0;
			}
		}
	}
}
//先操作,再选择。 
void sovle()
{
	int i;
	int x1,x2,x3;
	x1=b[2]*(b[7]*100+b[8]*10+b[9]);
	x2=b[3]*(b[4]*100+b[5]*10+b[6]);
	x3=(10-b[1])*(b[7]*100+b[8]*10+b[9])*b[3];
	if(x1+x2==x3)
	{
		sum++;
	}
}

这道题目,我们看都不用看,果断选择回溯,因为,题意就是对9个数进行排列,然后,找到一个符合标准的排列时,加1.同时回溯法也是解决排列组合的一种很好的方法。但是这道题目麻烦在精度上面,我们必须要对分母进行通分。这个地方是出题人的一个变化的地方,因为以前考的都不用考虑精度,为啥呢?因为就比如2015年的三羊献瑞一样,它是乘法加法运算,算出的都是整数。

当然,我建议在考场直接用c++的next_permutation()这个函数,速度快。

#include
#include
using namespace std;
int main()
{
	int x1,x2,x3;
	int a[9];
	int i;
	int sum=0;
	for(i=0;i<9;i++)
	{
		a[i]=i+1;
	}
	while(next_permutation(a,a+9))
	{
		x1=a[1]*(a[6]*100+a[7]*10+a[8]);
		x2=a[2]*(a[3]*100+a[4]*10+a[5]);
		x3=(10-a[0])*(a[2]*(a[6]*100+a[7]*10+a[8]));
		if(x1+x2==x3)
		{
			sum++;
		}
	}
	printf("%d\n",sum);
	return 0;
}

模型二:

   2016蓝桥杯第6题:方格填数;

   

方格填数

如下的10个格子

(如果显示有问题,也可以参看【图1.jpg】)

填入0~9的数字。要求:连续的两个数字不能相邻。
(左右、上下、对角都算相邻)

一共有多少种可能的填数方案?

请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。


          我的分析:这道题目,在回溯法应用于排列中算是一道比较有创意的题,首先,填数的过程就是一个排列,而排列往往用回溯,因为回溯法专门对那种解空间已知的,且解的成分独立,不相同。首先,我们要有方格存储,我们定义一个二维数组即可。在填数的过程中,应划分三块(按次数哈)。还有一点注意就是填数是有顺序的,就是第二个填入的数只需和第一个比较即可,不用和上下左右都比较。(我第一次做就犯了这个低级的错误)。
#include

using namespace std;
int graph[3][4]={0};
int visited[10]={0};  //候选集 
int sum=0;
void dfs(int k,int i);
int main()
{
	int j;
	for(j=0;j<=9;j++)
	{
		dfs(1,j);
		visited[j]=0;
	}
	printf("%d\n",sum);
	return 0;
} 

void dfs(int k,int i)
{
	int x1,x2;
	int j;
//	int j1,j2;
	visited[i]=1;
	//k决定了对哪个框进行操作。 
	//以下就是进行操作。 
	if(k>=8)
	{
		x1=2;
		x2=k%8;
		graph[x1][x2]=i;
		//下面是检验; 
		if(x2==0)
		{
			if((graph[x1-1][x2]-graph[x1][x2]==1)||(graph[x1-1][x2]-graph[x1][x2]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==1))
			{
				return;
			}
		}
		else
		{
			if(x2==1)
			{
				if((graph[x1-1][x2]-graph[x1][x2]==1)||(graph[x1-1][x2]-graph[x1][x2]==-1)||(graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2-1]==1)||(graph[x1][x2]-graph[x1-1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==1)||(graph[x1][x2]-graph[x1-1][x2+1]==-1))
				{
					return;
				}
			}
			else
			{
				if((graph[x1-1][x2]-graph[x1][x2]==1)||(graph[x1-1][x2]-graph[x1][x2]==-1)||(graph[x1][x2]-graph[x1-1][x2-1]==1)||(graph[x1][x2]-graph[x1-1][x2-1]==-1)||(graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==1)||(graph[x1][x2]-graph[x1-1][x2+1]==-1))
				{
					return;
				}
			}
		} 
	}
	else
	{
		if(k>=4)
		{
			x1=1;
			x2=k%4;
			graph[x1][x2]=i;
			if(x2==0)
			{
				if((graph[x1-1][x2+1]-graph[x1][x2]==1)||(graph[x1][x2]-graph[x1-1][x2+1]==1))
				{
					return;
				}
			}
			else
			{
				if(x2==1)
				{
					if((graph[x1-1][x2]-graph[x1][x2]==1)||(graph[x1-1][x2]-graph[x1][x2]==-1)||(graph[x1][x2]-graph[x1][x2-1]==-1)||(graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1-1][x2+1]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==1))
					{
						return;
					}
				}
				else
				{
					if(x2==2)
					{
						if((graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2-1]==1)||(graph[x1][x2]-graph[x1-1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2]==1)||(graph[x1][x2]-graph[x1-1][x2]==-1)||(graph[x1][x2]-graph[x1-1][x2+1]==1)||(graph[x1][x2]-graph[x1-1][x2+1]==-1))
						{
							return;
						}
					}
					else
					{
						if((graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2-1]==1)||(graph[x1][x2]-graph[x1-1][x2-1]==-1)||(graph[x1][x2]-graph[x1-1][x2]==1)||(graph[x1][x2]-graph[x1-1][x2]==-1))
						{
							return;
						}
					}
				}
			} 
		}
		else
		{
			x1=0;
			x2=k%4;
			graph[x1][x2]=i;
			if(x2==2)
			{
				if((graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1))
				{
					return;
				}
			}
			else
			{
				if(x2==3)
				{
					if((graph[x1][x2]-graph[x1][x2-1]==1)||(graph[x1][x2]-graph[x1][x2-1]==-1))
					{
						return;
					}
				}
			}
		}
		
	}
	
	if(k==10)
	{
		sum=sum+1; 
		return;
	}
	else
	{
		for(j=0;j<=9;j++)
		{
			if(visited[j]==0)
			{
				dfs(k+1,j);
				visited[j]=0;
			}
		}
	}
}
//先进行操作,然后,深搜, 
此时对我的搜获其实挺大的,因为,以前回溯法解决排列时,大多是算式的题目,最后都回溯到相应k,然后进行一波操作。但是此题,我在做时并没这么做,visited[10]的工作当然一开始就弄了,但是在存数的过程中,存完后我也进行相应的比较了,即验证工作放到了每一步过程中。这种处理方法主要是受蓝桥杯上的lift and throw那道题的影响。of couse ,如果把验证放到最后,也是可以的,那这样的话,直接用c++的模板即可。

但是,并不是验证过程放到最后就万事大吉的,有些时候,该还的还得还。
模型三:
      题目:猜算式
你一定还记得小学学习过的乘法计算过程,比如:
   273
x   15
------
  1365
  273
------
  4095
  
请你观察如下的乘法算式
    ***
x   ***
--------
    ***
   ***
  ***
--------
  *****
星号代表某位数字,注意这些星号中,
0~9中的每个数字都恰好用了2次。
(如因字体而产生对齐问题,请参看图p1.jpg)
请写出这个式子最终计算的结果,就是那个5位数是多少?
注意:只需要填写一个整数,不要填写任何多余的内容。比如说明文字。

分析:   我拿到这道题的时候,第一感觉也是回溯法,回溯20个数,然后拿到后,再最后进行一波验证。包括,我那天晚上把这道题目拿给去年获得一等奖的室友看,他的想法也是如此,直接回溯,信心满满的。。。。。可是的话,这道题目用回溯解真的不合适,因为,它有20个位置变量需要填充。哪怕,你像我上一题一样中途验证也没用。出不来结果。其实解题,真的是一门学问,回溯法虽然可以解决这种解空间明确,且元素只用一次的题目,但一旦需要排列的数超过一定数就基本不得行了。     然后我们仔细的看题意,发现只要我们知道前六个,就可以依次算出后面的所有。然后在这我又犯了一个错误,直接暴力了六个数----2333333,直接枚举两个数可好。。。。。总而言之,此题值得我们对回溯法使用时要谨慎。并且以后遇到此类模型题,不要犹豫,就是简单枚举。
#include
using namespace std;
int a[6];
int pd();
int main()
{
	int i,j;
	for(i=100;i<999;i++)
	{
		for(j=100;j<999;j++)
		{
			a[0]=i;
			a[1]=j;
			a[2]=i*(j%10);
			a[3]=i*(j/10%10);
			a[4]=i*(j/100%10);
			a[5]=i*j;
			if(a[2]<100||a[2]>999)
			{
				continue;
			}
			if(a[3]<100||a[3]>999)
			{
				continue;
			}
			if(a[4]<100||a[4]>999)
			{
				continue;
			}
			if(a[5]<10000||a[5]>99999)
			{
				continue;
			}
			if(pd())
			{
				printf("%d\n",a[5]);
				break;
			} 
		}
	}
	return 0;
}
int pd()
{
	int t;
	int i;
	int x; 
	int b[10]={0};
	for(i=0;i<6;i++)
	{
		t=a[i];
		while(t>0)
		{
			x=t%10;
			b[x]++;
			if(b[x]>2)
			{
				return 0;
			}
			t=t/10;
		}	
	}
	return 1;
}
还有注意的一点,注意,对后面这几个进行一个位数的限制。

模型四:
     2016  第7题
     
第七题:


剪邮票

如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。

请你计算,一共有多少种不同的剪取方法。

请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。





分析:此题是非常值得学习的一道,显然它并不是数的排列,我们观察到的是数的组合,那怎么来思考呢?一般会想到两种方案:一种是原始的路径搜索问题,因为,在这其中就像迷宫一样,需要我们选一个起始点,然后依次深搜,到边界就返回。但是这种想法藐视不行,行了也很复杂。故我采用的是第二种,因为毕竟这张方格以数为主,则可以用组合。然后对这个组合进行验证判断(用深搜判断即可)。

#include
#include
using namespace std;
int visited[13]={0};
int b[6]={0};//这样做适合排列,不适合组合
int huoxuan[13]={0}; 
int sum=0;
void dfs(int k,int i);
void dfs1(int i);
void sovle();
int main()
{
	dfs(0,0);
	printf("%d\n",sum);
	return 0;
}

void dfs(int k,int i)
{
	int j;
	visited[i]=1;
	b[k]=i;
	if(k==5)
	{
		sovle();
		return;
	}
	else
	{
		for(j=1;j<=12;j++)
		{
			if(visited[j]==0&&j>b[k])
			{
				dfs(k+1,j);
				visited[j]=0;
			}

			
		}
	}
}
//组合再修改毋庸置疑. 
//我下面这种解决方案是很值得思考的,即根本不符合题意。 
//往大了取; 
void sovle()
{
	int i,j;
	int x;
	int flag=0;
	for(i=1;i<13;i++)
	{
		huoxuan[i]=visited[i];
	}
    for(i=1;i<13;i++)
	{
		if(huoxuan[i]==1)
		{
			break;
		}
		
	}	
	//拿到最小的那个。 
	dfs1(i);
	
	for(i=1;i<13;i++)
	{
		if(huoxuan[i]==1)
		{
			break;
		}
	}
	if(i==13)
	{
//		for(i=1;i<13;i++)
//		{
//			if(visited[i]==1)
//			{
//				printf("%d ",i);
//			}
//		}
//		printf("\n");
		sum++;
	}
	
	
}

void dfs1(int i)
{
	
	int x1,x2;
	if(i>=13)
	{
		return;
	}
	huoxuan[i]=0;
	if(i-4>0)
	{
		if(huoxuan[i-4]==1)
		{
			dfs1(i-4);
		}
	}
	if((i-1)%4>0)
	{
		if(huoxuan[i-1]==1)
		{
			dfs1(i-1);
		}
	}
	if((i+1)%4!=1)
	{
		if(huoxuan[i+1]==1)
		{
			dfs1(i+1);
		}
	}
	if((i+4)<13)
	{
		if(huoxuan[i+4]==1)
		{
			dfs1(i+4);
		}
		
	} 
}


总结,当然,在搜索这块地题目还有很多,比如八数码,倒水问题,但是,我觉得在解决题目的时候,思路要清晰,比如回溯法吧,它并不是是个搜索就能去解决的,并且其写法也多样,扩展验证,还是最后验证,这要看个人,它也常常使用在排列,已经解空间已知的状况。而路径搜索问题,则常常采用的是深搜和广搜,这有什么特点呢?一般来说解决有初始目标 有终极目标的问题,并且在这其中,每一步的获选集是不同的,可能一个可能两个。如果每次获选集一样这就是传说中的树的遍历了,题目也很多。





你可能感兴趣的:(算法)