搜索专练

我不知道怎么才能将博客写的好一点,详细一点。
最近讲搜索,有些不好掌握,因此,就先刷题吧,写一些解析,我的理解。
按照oj上的顺序;

NO.1:火柴棒(noip2008提高)

这个有点难说,我记得我好像已经写过一次了,但是自认为不是很好,所以重写一下,看到noip提高整个人都不好了。
题目描述: 给你n根火柴棍,你可以拼出多少个形如“A+B=C”的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零,则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示:
就是这货
注意:
1.加号与等号各自需要两根火柴棍(一看就知道)
2.如果A≠B,则A+B=C与B+A=C视为不同的等式(A、B、C>=0)
3.n根火柴棍必须全部用上
看到题目,我们首先就会想到每个数字的火柴数怎么存,哈哈哈,一个神奇的方法,就这样存呗:

    a[0]=6;
	a[1]=2;
	a[2]=5;
	a[3]=5;
	a[4]=4;
	a[5]=5;
	a[6]=6;
	a[7]=3;
	a[8]=7;
	a[9]=6;

呵呵,很简单,很草率,不过这个还是要细心的,你那个数字存错了不就炸了嘛。
接下来,我们发现,题目要求我们计算一个用n个火柴棒拼成的加法算式,因此不管怎么拼,这个算式里都会有一个加号和一个等号,所以,我们首先用总的火柴数减去这两个号(有点草率)所用的火柴数4:cin>>n;n-=4;
酱紫我们只要枚举数字所用的火柴数即可。
该咋办,很草率,很暴力:
我们利用一位数所需的火柴数,进而求出多位数所用的火柴数,因为不管是什么数都是由0~9组成的,可以枚举到大一点的数,每次取模,从低位开始分,就是酱紫的啦:

int k;
    for(int i=10;i<=5000;i++)
     {
     	int k=i;
     	while(k>0)
     	{
     		a[i]=a[i]+a[k%10];
     		k=k/10;
     	}
     }

然后嘞,我们晓得A的火柴数加B的火柴数加等于C的火柴数要等于当前的n,
又因为A+B=C
由此我们可得a[A]+a[B]+a[C]=n——>a[A]+a[B]+a[A+B]=n
代码参见:

for(int i=0;i<=1000;i++) 
     for(int j=0;j<=1000;j++) 
       if(a[i]+a[j]+a[i+j]==n) s++;
       cout<<s<<endl;

最后,我们将它们合并在一起,就是解了,一切都那么顺畅自然,下一个。

NO.2:亲和数

题目是个啥,好像有点忘了,好久以前做的;
题目描述:
你是个好童鞋,有一天 看了一本趣味数学书,上面提到了亲和数:定义数对 (x,y) 为亲和数对当且仅当 x 、 y 为不同正整数,且 x 、 y 各自的所有非自身正因子之和等于另一个数
例如 (220,284) 和 (280,224) 都是亲和数对,因为: 220 的所有非自身正因子之和为: 1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110 = 284 284 的所有非自身正因子之和为: 1 + 2 + 4 + 71 + 142 = 220 数对 (x,y ) 跟 (y,x) 被认为是同一数对,所以我们只考虑 x小于y 的情况。
任务:编写一个程序计算给定范围内的亲和数对的数量。给定一个范围 A 到 B , 如果 A ≤ x ≤ B ,则我们称 (x,y) 在范围 [A,B] 内,A 、 B 满足 1 ≤ A ≤ B ≤ 10^8 且 B-A ≤ 10^5 。
这题目的字可是真的多…
进入正题:
我们定义一个函数
work()
,来求这个数的不包括自己的所有因子,这个我想不用多讲:

int  work(int k)
 {
   int x; 
   x=0;
   for(int i=1;i*i<=k;i++)
    if(k%i==0)x+=i+k/i;//i为因子,则k/i也一定为因子 
   return x-k; //因子总和不包括自身 
 }

简单,明了。
接下来的,有一小点的绕,我们知道work(x)表示除x本身外x所有的因子和,根据题意y=work(x),而x=work(y),套入(俄罗斯套娃),得y=work(work(x))。 同时题目要求x小于y。
在A~B的范围内枚举其中一个数 ,我们只要判断if(work(work(i))==i&&work(i)>i) 就可以知道这对数是不是亲和数。
代码拼接后如下:

#include<bits/stdc++.h>
using namespace std;
int sum1,sum2;
int  work(int k)
 {
   int x; 
   x=0;
   for(int i=1;i*i<=k;i++)
    if(k%i==0)x+=i+k/i;//i为因子,则k/i也一定为因子 
   return x-k; //因子总和不包括自身 
 }
int main()
{
	int a,b;
	int s=0;
	cin>>a>>b;
	for(int i=a;i<=b;i++)
	 {
	   if(work(work(i))==i&&work(i)>i)s++;
	   //work(i)是对第一个数取因子和并返回,因为另一个数为当前数因子和 ,
	   //因此work(work(i))【另一个数的因子和】要等于这个数
	   //根据题意x小于y
	 }
	 cout<<s<<endl;
	return 0;
} 

上面两题给人一种不是搜索的感觉,下面有两道题我在之前的博客里合起来写的,有点方。
最大连续子矩阵累计和
NO.3:细胞问题:

又是题目描述:
一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如阵列:
0234500067
1034560500
2045600671
0000000089
有4个细胞。

像这种网格图,一看就知道可能要用神奇的广搜(哦,可爱的bfs),这题实际上还是蛮简单的。
我们在搜索这个矩阵的时候,找到一个不为0的数,表示使细胞的一部分,然后开始搜索,当然,之后要将这个数标记为0表示已经搜索过,搜索:向上,下,左,右渗透
like this:

void bfs(int x,int y)
{
	if(a[x][y])//是细胞 
	{
	 a[x][y]=0;
	 bfs(x+1,y);//向下 
	 bfs(x-1,y);//向上 
	 bfs(x,y+1);//向右 
	 bfs(x,y-1); //向左 
    }
	
}

但是有没有注意到,我们输入的时候是每行一个字符串,但这个很好办:

for(int i=1;i<=m;i++)
	{
	  cin>>s;
	 for(int j=1;j<=n;j++)
	  a[i][j]=s[j-1]-'0';//注意S[]数组是存字符串的
    }

其中m是矩阵的行,n是矩阵的列,像酱紫,我们就可以在a[][]里存上数字了,然后遍历二维数组a[][],进行判断搜索即可。(顺序有乱哈~)
代码就不全都放出了…

NO.4:跳房子(usaco的题)

唉,让人回忆起童年;
题目又描述:
我们创造了 一个5x5的、由与x,y轴平行的数字组成的直线型网格,而不是用来在里面跳 的、线性排列的、带数字的方格。这样的网格,一行5个整数 保证这里的整数在[0,9]之间。
然后我们熟练地在网格中的数字中跳:向跳、向跳、向跳、向跳 (从不斜过来跳),跳到网格中的另一个数字上。我们再这样跳啊跳(按相同规则),跳到另外一个数字上(可能是已经跳过的数字)
一共在网格内跳过五次后,我们的跳跃构建了一个六位整数(可能以0开头, 例如000201)
求出所有能被这样创造出来的不同整数的总数。(真是闲着没事干)

看了题之后:这是个啥玩意儿。
简单来说,就是让我们求网格中相连的一串数字,所组成的六位数有多少个。
因此我们要开一个bool数组来记录已经查询过的六位数;还有一个bool二维数组来判断此点是否能走;

又是网格图,又是“比方说”(BFS)
在搜索的时候有那么几个变量
bfs(int 向上或向下的方向(坐标x),int 向左或向右的方向(坐标y),int 因为跳房子而不断更新的数字,int 当前是第几步)

for(int i=1;i<=5;i++) 
     for(int j=1;j<=5;j++)
	  bfs(i,j,0,1);//初始

void bfs(int x,int y,int s,int k)
{
	if(k>6)//如果已经是六位数则不用再走下去
	{
		if(!c[s])//新的六位数 
		{
			c[s]=1;
			ans++; //总数++
		}
		return;
	}
	//不同方向 
	if(b[x+1][y])f(x+1,y,s*10+a[x][y],k+1);
	if(b[x-1][y])f(x-1,y,s*10+a[x][y],k+1);
	if(b[x][y+1])f(x,y+1,s*10+a[x][y],k+1);
	if(b[x][y-1])f(x,y-1,s*10+a[x][y],k+1);
    //s*10+a[x][y]:走的时候,数位不断增加
}

原先真的是一头雾水,不知道该怎么做,但主要还是要找题目的中心,它到底让我们求什么,是要怎么进行操作的,把题目中的故事转换为代码实现,其实就是要“死板”点,把自己想成机器人,按照步骤一步步进行下去。
之后的代码就不必给全了。

NO.5:迷宫

题目描述: 应该都知道的。
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,问每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案。在迷宫中移动有上下左右四种方式。保证起点上没有障碍。
会做,非常开心。
**输入:**第一行N、M和T,N为行,M为列,T为障碍总数。 第二行起点坐标SX,SY,终点坐标FX,FY。 接下来T行,每行为障碍的坐标。

首先在读入的时候我们就要标记障碍物,接下来再dfs;
主程序:

int main()
{
	cin>>n>>m>>t;
	cin>>sx>>sy>>fx>>fy;
	for(int i=1;i<=t;i++) 
	{
	  int xx=0,yy=0;
	  cin>>xx>>yy;
	  a[xx][yy]=true;//标记障碍物 
    }
    dfs(sx,sy);//搜索
    cout<<ans<<endl;
	return 0;
}

接下来,主要的搜索,看到这个什么方向的,应该是要用方向数组的

int xl[4]={-1,0,1,0};//坐标更改,分别是,下,右,下,左
int yl[4]={0,1,0,-1};

在搜索的时候,要注意边界,是否越界:

	if(x<=0||x>n||y<=0||y>m)//是否越界 
	  return;

以及是否到达终点,到终点了就不用继续走了:

if(x==fx&&y==fy)//到达终点 
	 {ans++;return;}//方案数++

在每次搜索的时候都要标记当前点已经被搜索过:

a[x][y]=true;//标记 

下面是搜索的关键:

for(int i=0;i<4;i++) 
	 if(!a[x+xl[i]][y+yl[i]])//不同方向移动 
	   dfs(x+xl[i],y+yl[i]);

最后,不能忘了回溯:

a[x][y]=false;//回溯 
	return;

这题就完结了;

这篇博客就先到这,一次性写完所有题不是很好,慢慢吸收。
预知后题如何,请看下集

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值