前言:搜索有俩大几乎万能的法器:DFS与BFS
本文介绍DFS
BFS可以参考广度优先搜索(BFS)
1,算法特点:
DFS是一个莽夫,在遇到死路之前,他只会一根筋往前走,不回头(形象比喻)
所以算法特点就是每次一直往节点走下去,直到没有节点,就返回上一个节点看看还有没有其他节点,有就继续,没有就还是返回上上个节点,直到全部遍历完为止
2,基本思路:
bool vis[N];
void dfs(){
if()return;//结束搜索的条件
for( ; ;){
if(vis[x])continue;//如果这个点已经在目前这个路径走过,跳
vis[x]=1;//标记该点访问过
dfs(x+1);//往深处dfs
vis[x]=0;//出来就标记为没有访问过,因为我走了
}
}
3,例题一:
思路:很明显,就普通的写dfs肯定能得到答案,但是肯定会被TLE。
我们注意到:(假设起点(sx,sy),终点(ex,ey),要求时间T
1,从起点到终点最少需要abs(ex - sx) + abs(ey - sy)的时间,如果T比他小,不考虑
2,除去墙的数目count,如果T>n*m-count,那么我把所有格子走完时间也没到,不考虑
3,我们会发现,从起点到终点,如果起点坐标和是偶数,终点坐标和是偶数(俩个奇偶性相同),那么肯定要走偶数步,如果起点坐标和是偶数,终点坐标和是奇数(奇偶性不同),要走奇数步............所以奇偶性相同,走偶数步,不同奇数步,不符合,舍去,即(ex + ey)%2 ^ (sx + sy)%2) ^ (T % 2)(相同异或为0)
通过剪枝,我们才可避免TLE
AC代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const int N = 100;
char arr[N][N];
bool vis[N][N];
int n, m, T;
int sx, sy, ex, ey;
bool flag;
int way[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};//4个方向的基向量
void dfs(int x, int y, int t)
{
if (flag)return;//如果已经找到符合结果,不用往下了
if (x == ex && y == ey && t == T)
{
flag = 1;
return;
}
if (t >= T)return;//时间过了也不用往下了
for (int i = 0; i < 4; ++i)
{
int tx = x + way[i][0], ty = y + way[i][1];
if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && !vis[tx][ty] && arr[tx][ty] != 'X')//要求:坐标合法,没有标记过,这个点可以走
{
vis[tx][ty] = 1;
dfs(tx, ty, t + 1);
vis[tx][ty] = 0;
}
}
}
int main()
{
while (cin >> n >> m >> T && n + m + T != 0)
{
memset(vis, 0, sizeof(vis));
int count=0;
for (int i = 1; i <= n; ++i)for (int j = 1; j <= m; ++j)
{
cin >> arr[i][j];
if (arr[i][j] == 'S')
{
sx = i, sy = j;
}
else if (arr[i][j] == 'D')ex = i, ey = j;
else if(arr[i][j]=='X')count++;
}
if (abs(ex - sx) + abs(ey - sy) > T||T>n*m-count||((ex + ey)%2 ^ (sx + sy)%2) ^ (T % 2))
{
cout << "NO" << endl;
continue;
}
flag = 0;
vis[sx][sy] = 1;//起点先标记为访问过
dfs(sx, sy, 0);
if (flag)
{
cout << "YES" << endl;
}
else
{
cout << "NO" << endl;
}
}
return 0;
}
4,例题2:
思路:同样,你知道我要说什么,直接正面刚一定被TLE,那么我们思索哪里不必要
我们会发现,每个点可以获得的最大值是固定的,如果我们按常规,就好多求好多次,
我们想让他每个只求一次,那我们可以从大的点累积到小的点,但是我们不清楚谁是最优的较大(最大不一定最优)
聪明的你一定会想到dfs是可以从头深入到尾,那我们可以让他深入到最底层,再求(这样我们求出最底层的maxn(就是他自己的值),返回,我们就求出第二最底层的maxn,即maxn[x][y] = max(maxn[x][y], arr[x][y] + dfs(tx, ty));
而且,我们dfs只要保证能求出我当前要的maxn[x][y],后面有需要,直接返回这个值即可
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const int N = 200;
int way[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
ll arr[N][N];
ll maxn[N][N];
int n, k;
ll dfs(int x, int y)
{
if (maxn[x][y])return maxn[x][y];//初始化为0,如果你值不为0,说明其最大值已经求过,直接返回
maxn[x][y] = arr[x][y];//首先对于他自己(因为可能没有比他大的了
for (int i = 0; i < 4; ++i)for (int j = 1; j <= k; ++j)
{
int tx = x + way[i][0] * j, ty = y + way[i][1] * j;//以基向量与一步多长的j来决定去哪
if (tx >= 0 && tx < n && ty >= 0 && ty < n && arr[x][y] < arr[tx][ty])
{
maxn[x][y] = max(maxn[x][y], arr[x][y] + dfs(tx, ty));//我们最优走,只会一直往更大的值去,等他算完返回来,求可以求我当前要的maxn[x][y]
} //arr[x][y] + dfs(tx, ty),不漏arr[x][y],因为dfs只是深层的点,不包括当前这个点
}
return maxn[x][y];//最后return
}
int main()
{
while (cin >> n >> k && (k != -1 || n != -1))
{
for (int i = 0; i < n; ++i)for (int j = 0; j < n; ++j)cin >> arr[i][j];
cout << dfs(0, 0) << endl;
memset(maxn, 0, sizeof(maxn));
}
return 0;
}
5,例题3,状压优化dfsP1433 吃奶酪
思路:状态压缩,我们当进过相同点(且最后的点一样时,不同遍历会得到不同结果),但是,显然,我们只要最优的那个,那么我们发现数据n小于15,我们可以利用二进制表示点的存在状况,如01000101,就是第1,3,7个点有出现(1表示出现,0步出现),(理论如果用32个点),这样就能实现剪枝
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f;
const int N = 21;
vector<pair<double, double>>vc;//都是实数,不要有开成整型的了,特别是这个容器
double dis[N][N];//dis[i][j]记录i,j俩点的距离
double lu[(1 << 16) + 100][20];
bool vis[N];
double ans;
void dfs(int x, int way, double t, int n)//x表示当前点,way为二进制存储目前记录的点,t记录总路径,n表示剩余几个点没走
{
if (t >= ans)return; //超过当前最优就直接跳,最普遍的剪枝之一
if (n == 0)
{
ans = min(ans, t);
return;
}
for (int i = 1; i < (int)vc.size(); ++i)
{
if (!vis[i])
{
int tmp = way + (1 << (i - 1));
if ((!lu[tmp][i] || lu[tmp][i] > t + dis[x][i]) && t + dis[x][i] < ans)//!lu[tmp][i]是保证第一次进入赋予初始值
{
lu[tmp][i] = t + dis[x][i];
vis[i] = 1;
dfs(i, tmp, t + dis[x][i], n - 1);
vis[i] = 0;
}
}
}
}
int main()
{
int n;
double x, y;//实数
cin >> n;
vc.push_back({0.0, 0.0});
for (int i = 1; i <= n; ++i)
{
cin >> x >> y;
vc.push_back({x, y});
for (int j = 0; j < i; ++j)//预处理点之间的距离,避免dfs多次重复计算
{
dis[i][j] = dis[j][i] = sqrt(fabs(vc[i].first - vc[j].first) * fabs(vc[i].first - vc[j].first) + fabs(vc[i].second - vc[j].second) * fabs(vc[i].second - vc[j].second));
}
}
ans = INF * 1.0;//初始极大
vis[0] = 1;//起点标记访问过
dfs(0, 0, 0.0, n);
printf("%.2lf", ans);
return 0;
}