深度优先搜索(Depth-First-Search)
dfs是什么?
DFS是图论里面的一种搜索算法,他可以由一个根节点出发,遍历所有的子节点,进而把图中所有的可以构成树的集合都搜索一遍,达到全局搜索的目的。所以很多问题都可以用dfs来遍历每一种情况,从而得出最优解,但由于时间复杂度太高,我们也叫做暴力搜索。
啥时候用dfs?
当题目有如下特征,并且数据范围较小(时间复杂度太高)
1 . 地图型:这种题型将地图输入,要求完成一定的任务。因为地图的存在。使得题意清楚形象化,容易理清搜索思路
AOJ 869-迷宫(遍历地图,四向搜索) https://blog.youkuaiyun.com/chen_yuazzy/article/details/73656668
HDU 1035-Robot Motion(指定方向搜索,迷路(循环)判断) http://acm.hdu.edu.cn/showproblem.php?pid=1035
HDU 1045-Fire Net(check函数,回溯) http://acm.hdu.edu.cn/showproblem.php?pid=1045
HDU 1010-Tempter of the Bone(奇偶剪枝,回溯) http://acm.hdu.edu.cn/showproblem.php?pid=1010
POJ 1031棋盘问题 (类似八皇后问题)http://poj.org/problem?id=132
2 . 数据型:这种题型没有给定地图,一般是一串数字或字母,要求按照一定的任务解题。相对于地图型,这种题型较为抽象,需要在数据中进行搜索。数据以数组的形式存储,那么只要将数组也当作一张图来进行搜索就可以了。
HDU 1016-Prime Ring Problem(回溯、素数筛)
HDU 1258-Sum It Up(双重DFS递归,去重技巧)
HDU 1015-Safecraker(回溯,字符处理)
HDU 2676-Sudoku(抽象,回溯)
怎么用dfs?
结合下图模板及例题
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
void dfs(int step)
{
判断边界
{
相应操作
}
尝试每一种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
思维导图(图论里面的沧海一粟/(ㄒoㄒ)/~~)
P1706 全排列问题
题目描述
输出自然数 11 到 nn 所有不重复的排列,即 nn 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
输入格式
一个整数 nn。
输出格式
由 1 \sim n1∼n 组成的所有不重复的数字序列,每行一个序列。
每个数字保留 55 个场宽。
输入输出样例
输入 #1复制
3
输出 #1复制
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
说明/提示
1 \leq n \leq 91≤n≤9
#include<bits/stdc++.h>
using namespace std;
int a[101],b[101],n;
void print()
{
for(int i=1;i<=n;i++)
cout<<setw(5)<<a[i];
cout<<endl;
}
void dfs(int i)
{
if(i==n+1)// 判断边界
{
print();
return ;
}
for(int j=1;j<=n;j++)//尝试每一种可能
{
if(b[j]==0)//满足dfs条件
{
a[i]=j;
b[j]=1;//标记
dfs(i+1);//继续下一步
b[j]=0;//恢复初始条件,以便于回溯 (虚晃一枪)
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
dfs(1);
return 0;
}
P1294 高手去散步
题目背景
高手最近谈恋爱了。不过是单相思。“即使是单相思,也是完整的爱情”,高手从未放弃对它的追求。今天,这个阳光明媚的早晨,太阳从西边缓缓升起。于是它找到高手,希望在晨读开始之前和高手一起在鳌头山上一起散步。高手当然不会放弃这次梦寐以求的机会,他已经准备好了一切。
题目描述
鳌头山上有n个观景点,观景点两两之间有游步道共m条。高手的那个它,不喜欢太刺激的过程,因此那些没有路的观景点高手是不会选择去的。另外,她也不喜欢去同一个观景点一次以上。而高手想让他们在一起的路程最长(观景时它不会理高手),已知高手的穿梭机可以让他们在任意一个观景点出发,也在任意一个观景点结束。
输入格式
第一行,两个用空格隔开的整数n、m. 之后m行,为每条游步道的信息:两端观景点编号、长度。
输出格式
一个整数,表示他们最长相伴的路程。
输入输出样例
输入 #1复制
4 6
1 2 10
2 3 20
3 4 30
4 1 40
1 3 50
2 4 60
输出 #1复制
150
说明/提示
对于100%的数据:n≤20,m≤50,保证观景点两两之间不会有多条游步道连接.
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int g[N][N],dist,maxn=-10,n,m,x,y,z;
bool vis[N];
void dfs(int st)
{
for(int i=1;i<=n;i++)
{
if(g[st][i]&&!vis[i])
{
vis[i]=1;
dist+=g[st][i];
dfs(i);//由于i自加所以长得和别人不同
dist-=g[st][i];//恢复初始状态(回溯的时候要用到)
}
}
maxn=max(maxn,dist);//更新最大值
vis[st]=0;
return ;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
g[x][y]=z;
g[y][x]=z; // 双向图哦
}
for(int i=1;i<=n;i++)
{
vis[i]=1;
dfs(i);
memset(vis,0,sizeof(vis));//又回到最初的起点
}
cout<<maxn<<endl;
return 0;
}
P2196 挖地雷
题目描述
在一个地图上有NN个地窖(N \le 20)(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。
输入格式
有若干行。
第11行只有一个数字,表示地窖的个数NN。
第22行有NN个数,分别表示每个地窖中的地雷个数。
第33行至第N+1N+1行表示地窖之间的连接情况:
第33行有n-1n−1个数(00或11),表示第一个地窖至第22个、第33个、…、第nn个地窖有否路径连接。如第33行为1 1 0 0 0 … 011000…0,则表示第11个地窖至第22个地窖有路径,至第33个地窖有路径,至第44个地窖、第55个、…、第nn个地窖没有路径。
第44行有n-2n−2个数,表示第二个地窖至第33个、第44个、…、第nn个地窖有否路径连接。
… …
第n+1n+1行有11个数,表示第n-1n−1个地窖至第nn个地窖有否路径连接。(为00表示没有路径,为11表示有路径)。
输出格式
有两行
第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。
第二行只有一个数,表示能挖到的最多地雷数。
输入输出样例
输入 #1复制
5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1
输出 #1复制
1 3 4 5
27
#include<bits/stdc++.h>
using namespace std;
bool f[21][21],b[21];//f记录是否有路径相连,b检测这点是否走过
int a[21];//记录地雷数
int path[21],ans[21],cnt;//path记录路径,ans记录答案,cnt记录走了多少个点
int n;
int maxn;// 最大值
bool check(int x)
{
for(int i=1;i<=n;i++)
{
if(f[x][i]&&!b[i]) return false;//不满足条件返回false
}
return true;
}
void dfs(int x,int stp,int sum)
{
if(check(x))//判断边界
{
if(maxn<sum)
{
maxn=sum;
cnt=stp;
for(int i=1;i<=stp;i++)
ans[i]=path[i];
}//相应操作
return ;
}
for(int i=1;i<=n;i++)// 尝试每一种可能
{
if(f[x][i]&&!b[i])//满足check条件
{
b[i]=1;//标记
path[stp+1]=i;
dfs(i,stp+1,sum+a[i]);//继续下一步dfs
b[i]=0;//恢复初始状态,准备回溯
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];//输入地雷数量
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
cin>>f[i][j];//注意这里为单向边,区别于爬山的双向
for(int i=1;i<=n;i++)
{
b[i]=1;
path[1]=i;
dfs(i,1,a[i]);
b[i]=0;
}
for(int i=1;i<=cnt;i++)
cout<<ans[i]<<' ';
cout<<endl<<maxn;
return 0;
}
P1101 单词方阵
题目描述
给一n \times nn×n的字母方阵,内可能蕴含多个“yizhong
”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 88 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*
代替,以突出显示单词。例如:
输入:
8 输出:
qyizhong *yizhong
gydthkjy gy******
nwidghji n*i*****
orbzsfgz o**z****
hhgrhwth h***h***
zzzzzozo z****o**
iwdfrgng i*****n*
yyyygggg y******g
输入格式
第一行输入一个数nn。(7 \le n \le 1007≤n≤100)。
第二行开始输入n \times nn×n的字母矩阵。
输出格式
突出显示单词的n \times nn×n矩阵。
输入输出样例
输入 #1复制
7
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
输出 #1复制
*******
*******
*******
*******
*******
*******
*******
输入 #2复制
8
qyizhong
gydthkjy
nwidghji
orbzsfgz
hhgrhwth
zzzzzozo
iwdfrgng
yyyygggg
输出 #2复制
*yizhong
gy******
n*i*****
o**z****
h***h***
z****o**
i*****n*
y******g
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
const string cmp="yizhong";
int dx[]={1,1,1,-1,-1,-1,0,0};
int dy[]={1,-1,0,1,-1,0,1,-1};
char a[maxn][maxn],ans[maxn][maxn];
int mark[maxn][maxn],n;
void dfs(int x,int y)
{
for(int i=0;i<8;i++)
{
int flag=1;
for(int j=1;j<=6;j++)
{
int xx=x+j*dx[i];
int yy=y+j*dy[i];//虽然不是按什么条件深搜的,但这题一路搜到底很有dfs精神
if(xx<1||xx>n||yy<1||yy>n)
{
flag=0;
break;
}
if(cmp[j]!=a[xx][yy])
{
flag=0;
break;
}
}
if(flag==0) continue;
for(int j=0;j<=6;j++)//一定要从0开始,不然y会不见,别问我怎么知道的
{
int xx=x+j*dx[i];
int yy=y+j*dy[i];
ans[xx][yy]=a[xx][yy];
}
}
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cin>>a[i][j];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
if(a[i][j]=='y') dfs(i,j);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!ans[i][j]) ans[i][j]='*';
cout<<ans[i][j];
}
cout<<endl;
}
return 0;
}