我不知道怎么才能将博客写的好一点,详细一点。
最近讲搜索,有些不好掌握,因此,就先刷题吧,写一些解析,我的理解。
按照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;
这题就完结了;
这篇博客就先到这,一次性写完所有题不是很好,慢慢吸收。
预知后题如何,请看下集