<前言>
我惊讶的发现,其实我每天写的博客和每日总结也没区别了???
然而,今天讲到了dfs、bfs,那么…………
就让我来略扯一下吧!
haha~,胡cingger多有得罪啦!!
以下内容大部分来自PPT“dfs入门”与“bfs入门”;
DFS:
先看看什么是dfs吧,Dfs是“深度优先搜索”的英文简称,深度优先搜索是一种搜索方式,代码实现的载体基本上是递归函数,要求是对递归的结构必须完全理解。
基本上是按黑皮书的三个阶段来讲dfs:递归->回溯->dfs,递归会讲一些,但不会细讲,重讲回溯和dfs;
153. 拍卖
题目描述
一般情况下,拍卖行的拍卖师在拍卖商品的时候都是从低价开始起拍,由买方报价,最后谁出的价格高,商品就归谁所有。但海亮有个毛毛拍卖行,拍卖师毛毛在拍卖商品时正好相反:总是从高价开始起拍,如果没有人举成交牌就降价,而且毛毛在降价时还有规律:假如第i次报价为w元,那么第i+1次报价为w-a或者w-b元,如果降到p元时,你认为价格合适,赶快第一个举成交牌,你就花p元买下了商品。
任务:毛毛把商品从w元降到p元的方法总数。
输入格式
文件第一行有两个正整w 和p ,第二行有有两个正整a 和b. 1 ≤ w,p ≤ 10^6 , 2 ≤ a,b ≤ 10000, a不等于b.
输出格式
文件只有一行,即所求得的方法总数。注意:测试数据中方法总数不超过MAXlongint.
样例数据
10 3 2 3
3
数据规模与约定
保证2≤a,b≤100002≤a,b≤10000
时间限制:1s1s
空间限制:256MB
题目描述的问题请问乔治;
如题意,从w元有很多方法可以降成p元,但不变的是每一次只能降a元或b元,所以,我们可以定义一个递归函数search(Int start ,int end),start代表当前的拍卖金额状态,所以start一开始就是w,end代表结束时的金额,即为p。End不定义也可以,定义了方便理解。
void search(int start,int end)
{
if(start==end)
{
tot++;
}
else
{
if(start<end)
{
return ;
}
else
{
search(start-a,end);
search(start-b,end);
}
}
}
然后,这就是我们所谓的爆搜代码了,好吧,这就是爆搜代码,是会超时的,我们必须对代码进行优化,
其实加个记忆化就行了
那就加记忆化吧!
记忆化
顾名思义,就是对一些计算后的结果进行记录,然后直接调用即可,避免重复计算。也是空间换时间的思想。后面(明天肯定会讲到的)会讲的;
int search(int start,int end)
{
if(start==end)return 1;
if(start<end)return 0;
if(tot[start]>0)return tot[start];
tot[start]=search(start-a,end)+search(start-b,end);
return tot[start];
}
下面给出AC代码:
#include<bits/stdc++.h>
using namespace std;
long long w,p,a,b,tot[1300000];
int search(int start,int end)
{
if(start==end)return 1;
if(start<end)return 0;
if(tot[start]>0)return tot[start];
tot[start]=search(start-a,end)+search(start-b,end);
return tot[start];
}
int main()
{
cin>>w>>p>>a>>b;
search(w,p);
cout<<tot[w]<<endl;
return 0;
}
好的,那么我们dfs的第一步递归就算过了!(前路漫漫啊)
回溯例题:
#8mark问题(哈我居然翻出这题了)
题目描述
在一个n×n的棋盘上放置n个mark,要求所有的mark之间都不形成攻击。请你给出所有可 能的排布方案数。(注:Mark会攻击和他同行、同列、同对角线的其他生物)
输入格式 一个整数n
输出格式 一个整数表示方案数
样例数据
input
4
output
2
数据规模与约定
n<=8
时间限制:1s
空间限制:256MB
这是一个经典的回溯搜索题,就是对标记过的东西重新解除标记,以达到搜索全局所有可能性。
显然,这道题的思路就是尝试在每一行的每一个位置放下mark,然后对不能放的位置进行标记,每一次尝试放mark的时候检测当前位置有没有标记即可。
所以,我们需要三个标记数组,分别记录列,左斜线,右斜线。行通过递归函数的形参枚举即可。
记录斜对角线时我们可以发现,对角线行列号的差或和是不变的,所以标记数组的下标为行列号的差或和即可,对于左下和右上的对角线用le数组下标行列号求和标记,右下和坐上的对角线用ri数组下标行列号求差标记,避免负下标统一加n即可。
代码应该好实现吧,写完之后思考一下这题代码与上题的区别?
这就是回溯了,在搜索时,直接顺序搜索是无法得到所有解的。我们需要对刚刚进行的操作进行“撤回”,再试试其他位置能否有解。这句“相反”的,看似无用的语句一定要好好理解。
这样就没问题了!!
拓展:尝试打印每一组解(这还是我们13皇后时应该做的)
代码献上:
#include<bits/stdc++.h>
using namespace std;
int s=0,n;
bool zong[1001]={},you[1001]={},zuo[1001]={};
void digui(int ceng){
if(ceng==n){
s++;return;
}
for(int i=0;i<n;i++){
if(!(zong[i]||you[ceng-i+1]||zuo[ceng+i])){
zong[i]=you[ceng-i+1]=zuo[ceng+i]=1;
digui(ceng+1);
zong[i]=you[ceng-i+1]=zuo[ceng+i]=0;
}
}
}
int main()
{
cin>>n;
digui(0);
cout<<s;
return 0;
}
回溯就算讲完了吧(我的代码很渣是吧)
dfs搜索经典例题:
#解救Zero(迷宫)
题目描述
给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过。给定起点坐标和终点坐标,
问每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案,这样才能解救Zero。在迷宫中
移动有上下左右四种方式。保证起点上没有障碍。
输入格式
第一行N、M和T,N为行,M为列,T为障碍总数。 第二行起点坐标SX,SY,终点坐标FX,FY。
接下来T行,每行为障碍的坐标。
输出格式
给定起点坐标和终点坐标,问每个方格最多经过1次,从起点坐标到终点坐标的方案总数。
样例数据
input
2 2 1
1 1 2 2
1 2
output
1
数据规模与约定
1<=N,M<=5。
时间限制:1s1s
空间限制:256MB
这题可是个大工程啊,我们先得制定搜索的策略,可想而知,需要模拟的是每一步在合法的情况下如何走,搜索完每一种路线,统计方案总数即可。这也就是本题的大致思路。
首先我们又要用到这狗血无比的方向数组了:
dx[]={-1,0,1,0}
dy[]={0,-1,0,1}
还是讲讲方向数组吧:将两个数组dx和dy的同下标数字对应起来看,其实就是模拟了走迷宫四个方向走法发生在坐标系中的x,y轴变化请况,这样就可以用for循环快速简洁的对迷宫进行模拟遍历搜索了。这也是在众多遍历搜索题中必须要掌握的一点。
接下来就是边界的判断了:
停止搜索的三个条件:(1)越界,超出数组或n,m范围;(2)到达终点;(3)走到已走过的点(已标记)
可以将1),2)写在函数开头判断,3)写在遍历下一步时判断,再添上标记的语句和回溯取消标记的语句,就能写出搜索函数代码了。
那么,如果没问题就直接给代码了!!
#include<bits/stdc++.h>
using namespace std;
struct rode{
int x,y;
}st,en,x;
int n,m,s=0,vis[10][10]={},t;
void huisu(int x,int y){
if(x==en.x&&y==en.y){s++;return ;}
else{
vis[x][y]=1;
if(!vis[x][y-1]&&y-1>0)huisu(x,y-1);
if(!vis[x][y+1]&&y+1<=m)huisu(x,y+1);
if(!vis[x-1][y]&&x-1>0)huisu(x-1,y);
if(!vis[x+1][y]&&x+1<=n)huisu(x+1,y);
vis[x][y]=0;
}
return ;
}
int main()
{
scanf("%d%d%d",&n,&m,&t);
scanf("%d%d%d%d",&st.x,&st.y,&en.x,&en.y);
for(int i=1;i<=t;i++){
scanf("%d%d",&x.x,&x.y);
vis[x.x][x.y]=1;
}
huisu(st.x,st.y);
printf("%d",s);
return 0;
}
嘿,好像相比之下是我的代码通俗易懂一点(^∨^)!!
呼,dfs终于完了,那么……我们继续bfs!!
BFS
bfs:这是啥?
宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到 找到结果为止。
一般可以用它做什么呢?
一个最直观经典的例子就是走迷宫,我们从起点开始,找出到终点的最短路程,很多最短路径算法就是基于广度优先的思想成立的。
与深度优先搜索的对比:
深度优先搜索 用栈(stack)来实现,整个过程可以想象成一个倒立的树形:
1、把根节点压入栈中。
2、每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些 元素压入栈中。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
广度优先搜索 使用队列(可用数组代替)来实现,整个过程也可以看作一个倒立的树形:
1、把根节点放到队列的末尾。
2、每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱。
3、找到所要找的元素时结束程序。
4、如果遍历整个树还没有找到,结束程序。
好的以上内容都来自度娘,能理解多少就多少
bfs毫无花哨,来让我们直接开始吧:
#Mark坐电梯(奇怪的电梯): 题目描述: Mark有一天做了一个梦,他梦见他坐在一个电梯里。这个电梯很奇怪,它只能按当前楼层所
对应的按钮,每个按钮上有一个数字,表示按下这个按钮可以上升或下降几层楼。当然,如果不能
满足要求,相应的按钮就会失灵。
例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),从一楼开始。在一楼,按“上”可以到4楼,
按“下”是不起作用的,因为没有-2楼。现在Mark在A楼,他想要到B楼去,请问最少需要按几次按
钮。
输入样例: 5 1 5 //分别表示:有5层楼,Mark在1楼,他想到5楼 3 3 1 2 5 输出样例: 3
这已经是bfs的模板--了,就是入门的;
思路:
电梯只能上下移动
So?
我们先判断在当前楼层上下移动时,所到达的楼层是否合法(即不超过n层楼并且不低于1楼)。接着将所能到达的合法楼层进队,再去搜索它们。
想一想:我们要不要像dfs那样,判断当前楼层是否到达过?
答案是:要!!!但不用回溯;
我们来假设一下:若当前楼层是第2次到达,且有比第1次选择更优的方案,那么为什么不在第1次就选择它呢?(重点重点!!~)
因此重复来到过的楼层肯定不会是最优解,我们不必去考虑它。
直接给上代码:
#include<bits/stdc++.h>
using namespace std;
int a[501]={},b[501]={},c[501]={};
bool f[501]={};
int s=0,n,m,x,y,xx,yy,k=1,t;
void bfs()
{
int head=0,tail=1;
a[1]=x;
f[x]=true;
while(head>=tail)
{
head++;
if(a[head]-b[a[head]]>0&&!f[a[head]-b[a[head]]])//判断能否向下走
{
tail++;
a[tail]=a[head]-b[a[head]];
f[a[head]-b[a[head]]]=1;//将当前楼层标记为已访问过
c[a[head]-b[a[head]]]=c[a[head]]+1;
}
if(a[head]+b[a[head]]<=n&&!f[a[head]+b[a[head]]])//判断能否向上走
{
tail++;
a[tail]=a[head]+b[a[head]];
f[a[head]+b[a[head]]]=1;//将当前楼层标记为已访问过
c[a[head]+b[a[head]]]=c[a[head]]+1;
}
if(f[y]) return;//到达目标楼层,退出
}
}
int main()
{
scanf("%d%d%d",&n,&x,&y);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
bfs();
if(!f[y])
printf("-1\n");
else printf("%d\n",c[y]);
return 0;
}
啊,myc果然是大佬,你看看这代码,啧啧,条理清晰啊……(巴拉巴拉一大堆)
(请忽略上句话)仔细体会代码的写法,bfs重在、难在代码的理解与模拟上,这很重要,尤其是队列所起到的作用;
但我觉得昨天上午柴老师讲过了,(应该)都会吧!
下一题:
#山峰与山谷 题目描述: 心畅特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。为了满足心畅无敌的强迫症与
好奇心(这会害死他),他想知道山峰和山谷的数量。
给定一个地图,为心畅想要旅行的区域,地图被分为n*n的网格,每个格子(i,j) 的高度
w(i,j)是给定的。 若两个格子有公共顶点,那么他们就是相邻的格子。(所以与(i,j)相邻的格子有(i-1,
j-1),(i-1,j),(i-1,j+1),(i,j-1),(i,j+1),(i+1,j-1),(i+1,j),(i+1,j+1))。我们
定义一个格子的集合S为山峰(山谷)当且仅当:
1.S的所有格子都有相同的高度。 2.S的所有格子都联通。 3.对于s属于S,与s相邻的s’不属于S。都有ws>ws’(山峰),或者ws<ws’(山谷)。 你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,
那么整个地图即是山峰,又是山谷。
输入样例: 5 5 7 8 3 1 5 5 7 6 6 6 6 6 2 8 5 7 2 5 8 7 1 0 1 7 输出样例: 3 3
图副:
|5| 7 8 3 |1|
|5 5| 7 6 6
6 6 6 2 8
5 7 2 5 8
7 1 |0| 1 7
注:斜体加粗的是山峰,||内的是山谷
先来理解一下题目的意思:
在这张图中,我们可以看到,3座山峰周围的高度都比山峰的高度要低,3座山谷周围的高度都比山谷的高度要高
山峰与山谷的判定都是八个方向的(即上下左右、左上左下、右上右下)
理解题目的意思后是不是很简单!!!
#include<bits/stdc++.h>
using namespace std;
int ans1,ans2;
int n,now,mn,mx;
int a[1005][1005];
int xx[1000005],yy[1000005];
int dx[8]={1,1,1,-1,-1,-1,0,0},dy[8]={1,0,-1,1,0,-1,1,-1};//方向数组
bool f[1005][1005];
void bfs(int x,int y)
{
int head=0,tail=1;
xx[0]=x;yy[0]=y;
while(head!=tail)
{
int x=xx[head],y=yy[head];
head++;
for(int i=0;i<8;i++)//搜索8个方向
{
int nowx=x+dx[i],nowy=y+dy[i];//访问其中一个方向
if(nowx<1||nowy<1||nowx>n||nowy>n) continue;//判断是否越界
mx=max(mx,a[nowx][nowy]);
mn=min(mn,a[nowx][nowy]);
if(a[nowx][nowy]==now&&!f[nowx][nowy])//若可能属于同一个山峰或山谷
{
f[nowx][nowy]=1;
xx[tail]=nowx;
yy[tail]=nowy;
tail++;
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(!f[i][j])
{
now=a[i][j];
mn=now;
mx=now;
bfs(i,j);
if(mn<=now&&mx<=now) ans1++;
if(mn>=now&&mx>=now) ans2++;
}
printf("%d %d\n",ans1,ans2);
return 0;
}
那么这题就算过了哦
#282青铜莲花池请参考我的博客,专门有一篇的
<后记>
突然就有种为自己骄傲的感觉啊!!
这篇文章虽然没hzk大佬的大列表长,但字数大概是比他多了吧!
胡cingger你先别急着卧槽,我很不容易的,PPT上的不准复制代码,我都自己写的(还有用myc的)好不好,为了复制到字我多不容易
完结了!!!