图论:搜索问题
提到图论中的搜索问题,首先想到的也就是DFS和BFS了,而提到这两种搜索,那么最典型的题目就是岛屿问题了,下面就练习几道相关的题目,为之后的更深奥的图论学习打下基础!
孤岛的总面积
题目链接:孤岛的总面积
这道题,顾名思义就是求孤岛的面积之和,所谓的孤岛就是地图中间的岛屿,也就是需要将地图边界上的岛屿不考虑,那怎么不考虑呢?很简单,我们只需要遍历地图的四个边界,然后遇到一个陆地就用DFS将与之相连的所有陆地变为海洋即可,然后遍历一遍地图,如果还有陆地的话那么他,就是孤岛了,因为要求总面积,所以遇到一个陆地就将面积++即可。
//https://kamacoder.com/problempage.php?pid=1173
//孤岛的总面积
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 55;
vector<vector<int>> mp(N,vector<int>(N));
int n,m,ans=0;
void pre_handle()
{}
int dis[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};void dfs(int x,int y)
{mp[x][y] = 0;for(int i=0;i<4;i++){int tx = x + dis[i][0];int ty = y + dis[i][1];if(tx < 1 || tx > n || ty < 1 || ty > m) continue;if(mp[tx][ty] == 1) dfs(tx,ty);}
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>mp[i][j];for(int i=1;i<=n;i++) if(mp[i][1] == 1) dfs(i,1);for(int i=1;i<=n;i++) if(mp[i][m] == 1) dfs(i,m);for(int i=1;i<=m;i++) if(mp[1][i] == 1) dfs(1,i);for(int i=1;i<=m;i++) if(mp[n][i] == 1) dfs(n,i);for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)if(mp[i][j] == 1)ans++;cout<<ans<<endl;
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
沉没孤岛
题目链接:沉没孤岛
这道题和上一道题的思路是完全反过来即可,因为要让所有的孤岛沉没,所以就需要找出哪些是孤岛哪些不是孤岛,上一题中的代码已经将二者区分出来了,这道题我们只需要将标记为孤岛的陆地作为海洋输出即可,而非孤岛的陆地可以用其他的数字区分出来,如果用0的话就分不清是海洋还是陆地了,如果用1的话就分不清是否为孤岛了。
//https://kamacoder.com/problempage.php?pid=1174
//沉没孤岛
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 55;
vector<vector<int>> mp(N,vector<int>(N));
// vector<vector<bool>> v(N,vector<bool>(N));
int n,m,ans=0;
void pre_handle()
{}
int dis[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};void dfs(int x,int y)
{mp[x][y] = 2;for(int i=0;i<4;i++){int tx = x + dis[i][0];int ty = y + dis[i][1];if(tx < 1 || tx > n || ty < 1 || ty > m) continue;if(mp[tx][ty] == 1) dfs(tx,ty);}
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>mp[i][j];for(int i=1;i<=n;i++) if(mp[i][1] == 1) dfs(i,1);for(int i=1;i<=n;i++) if(mp[i][m] == 1) dfs(i,m);for(int i=1;i<=m;i++) if(mp[1][i] == 1) dfs(1,i);for(int i=1;i<=m;i++) if(mp[n][i] == 1) dfs(n,i);// for(int i=1;i<=n;i++)// for(int j=1;j<=m;j++)// if(mp[i][j] == 1) v[i][j] = 1;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(mp[i][j] == 2) cout<<1<<' ';else cout<<0<<' ';}cout<<endl;}
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
水流问题
题目链接:水流问题
这道题正向思维是遍历每一个点然后去搜索他附近的比他低的点,但是这样时间复杂度是很高的,所以我们可以借助逆向思维,因为他要求必须既能流到第一边界又能流到第二边界,所以我们可以从两个边界开始搜索比他高的位置,然后将所有的相邻的比他高的位置都给标记出来,这个操作可以用两个bool类型的数组来完成,最后只需要遍历每个点,如果这个点被两个标记数组给标记可达了,那么这个点就是所求的点,直接输出即可,思路很简单,但是实现起来较有难度。
//https://kamacoder.com/problempage.php?pid=1175
//水流问题
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 105;
vector<vector<int>> mp(N,vector<int>(N));
vector<vector<bool>> v1(N,vector<bool>(N,0)),v2(N,vector<bool>(N,0));
int n,m,ans=0;
void pre_handle()
{}
int dis[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};void dfs1(int x,int y)
{if(v1[x][y]) return ;v1[x][y] = true;for(int i=0;i<4;i++){int tx = x + dis[i][0];int ty = y + dis[i][1];if(tx < 1 || ty < 1 || tx > n || ty > m) continue;if(v1[tx][ty] == false && mp[tx][ty] >= mp[x][y]) dfs1(tx,ty);}
}
void dfs2(int x,int y)
{if(v2[x][y]) return ;v2[x][y] = true;for(int i=0;i<4;i++){int tx = x + dis[i][0];int ty = y + dis[i][1];if(tx < 1 || ty < 1 || tx > n || ty > m) continue;if(v2[tx][ty] == false && mp[tx][ty] >= mp[x][y]) dfs2(tx,ty);}
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>mp[i][j];for(int i=1;i<=n;i++) dfs1(i,1);//"1"for(int i=1;i<=n;i++) dfs2(i,m);//"2"for(int i=1;i<=m;i++) dfs1(1,i);//"1"for(int i=1;i<=m;i++) dfs2(n,i);//"2"for(int i=1;i<=n;i++){for(int j=1;j<=m;j++)if(v1[i][j] && v2[i][j])cout<<i-1<<' '<<j-1<<endl;}
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
建造最大岛屿
题目链接:建造最大岛屿
这道题是相对复杂的一道题了,想了很久的优化方法,后来看了题解,感觉又长脑子了,其实不需要对每一个点都去搜索,我们可以在一开始就先用普通的搜索方式将每一个岛屿都编号统一起来,编号要直接赋值地图上然后将对应的编号的岛屿的面积映射关系存起来,然后在遍历每一个为海洋的点的时候只需要看他的上下左右四个方向是否是岛屿然后将面积加起来即可,注意以下两个坑:
1.可能全部都是陆地,这时候就需要将存面积的数组中的值作为答案了
2.要注意海洋点的四个方向有可能存在相同编号的陆地,所以需要在对四个方向分析的时候用一个bool数组存起来作为访问标记,防止重复访问累加面积
代码实现起来极为恶心。
//https://kamacoder.com/problempage.php?pid=1176
//建造最大岛屿
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 55;
int s=0;
vector<vector<int>> mp(N,vector<int>(N));
unordered_map<int,int> f;
int n,m,ans=0;
void pre_handle()
{}
int dis[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};void dfs(int x,int y,int mark)
{s++;mp[x][y] = mark;for(int i=0;i<4;i++){int tx = x + dis[i][0];int ty = y + dis[i][1];if(tx < 1 || tx > n || ty < 1 || ty > m) continue;if(mp[tx][ty] == 1) dfs(tx,ty,mark);}
}
void solve()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>mp[i][j];int index=2;//岛屿的编号、面积for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(mp[i][j] == 1){s=0;dfs(i,j,index);f[index++] = s;}}}int ans=0;for(auto i : f) ans = max(ans,i.se);//防止没有0的情况 !!!for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(mp[i][j] == 0){unordered_map<int,bool> v;int t=1;if(mp[i-1][j] && !v[mp[i-1][j]]){t+=f[mp[i-1][j]];v[mp[i-1][j]] = true;}if(mp[i+1][j] && !v[mp[i+1][j]]){t+=f[mp[i+1][j]];v[mp[i+1][j]] = true;}if(mp[i][j-1] && !v[mp[i][j-1]]){t+=f[mp[i][j-1]];v[mp[i][j-1]] = true;}if(mp[i][j+1] && !v[mp[i][j+1]]){t+=f[mp[i][j+1]];v[mp[i][j+1]] = true;}ans = max(ans,t);}}}cout<<ans<<endl;
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
岛屿的周长
这道题其实不是一道搜索题,直接遍历一遍将每个陆地点的上下左右四个方向的情况做一个判断即可,但是需要注意的是,周长需要++的情况就是1.该点作为陆地边界 2.该点作为地图边界,其实归根结底,都是遇到边界上的陆地就周长++,所以用1为初始的下标索引可以更简便的统计周长,因为不管是哪一种边界,都不会导致数组越界而且都是0。
//https://kamacoder.com/problempage.php?pid=1178
//岛屿的周长
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 55;
vector<vector<int>> mp(N,vector<int>(N));
int n,m,ans;
void pre_handle()
{}
int dis[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};void dfs(int x,int y)
{}
void solve()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>mp[i][j];for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){if(mp[i][j] == 1){if(mp[i-1][j] == 0) ans++;if(mp[i+1][j] == 0) ans++;if(mp[i][j+1] == 0) ans++;if(mp[i][j-1] == 0) ans++;}}}cout<<ans<<endl;
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
字符串接龙
题目链接:字符串接龙
这是一道结合了建图以及最短路的题,一般无权图求最短路直接用搜索即可,这道题用BFS即可
这道题有两个难点:怎么建图,怎么求最短路?
建图的还就需要对每个字符串进行枚举所有可能变成的字符串,如果在字典中出现了,就说明这两个字符串之间是可达的,可达的情况下就去判断是否已经搜索过这个点了(因为BFS要求一个点只能搜一遍),在没有搜索过的条件下再加入队列开始搜索。
整体思路如下:
用一个set来储存字典中的单词(因为元素是string类型,而相对map来说内存更小)是只读判断存在状态的最佳容器。
然后用一个map存放每个单词以及映射的路径长度(和普通的BFS一样 只不过普通的BFS在两个数组中存放了当前位置的路径长度以及访问状态,这是string类型,只能利用map的映射关系来实现既能对是否访问的判断又能储存路径长度)
最后就是枚举了,从BFS开始的队首元素开始不断的枚举所有的能变成的字典中的元素,第一次达到就直接入队进行搜索即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se secondvoid pre_handle()
{} void solve()
{int n;cin>>n;string s1,s2;cin>>s1>>s2;unordered_set<string> st;//字符串字典unordered_map<string,int> mp;//用于存放从起始字符串到当前的最短距离(通过BFS计算)//同时还可以记录该字符串是否被访问过了for(int i=1;i<=n;i++){string x;cin>>x;st.insert(x);}queue<string> q;q.push(s1);mp[s1] = 1;while(!q.empty())//BFS{string now = q.front();q.pop();int step = mp[now];for(int i=0;i<now.size();i++){string t = now;for(int j=0;j<26;j++)//遍历每一种情况 如果在字典中出现就说明可以连起来{t[i] = 'a' + j;if(t == s2){cout<<mp[now] + 1;return ;}//可以连起来的话就可以进行搜索了,但是BFS的前提是每个点只访问一次 所以当前字符串必须是首次出现if(st.find(t) != st.end() && mp.find(t) == mp.end()){q.push(t);mp[t] = step + 1;}}}}cout<<0<<endl;
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
有向图的完全联通
点少边多:邻接矩阵
点多边少:邻接表
这道题是有向图的入门题目
首先需要将图建立起来,然后用dfs去遍历所有可达点即可,如果能遍历到所有点就是1否则就是-1
注意图的遍历的话需要有递归终止条件,每个点必须值访问异一次,防止死循环。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e2+10;
vector<int> e[N];
vector<bool> v(N,0);
void pre_handle()
{}
void dfs(int x)
{if(v[x] == true) return ;//防止重复访问v[x] = true;//存的都是可达点 所以无需判断for(auto i : e[x]) dfs(i);
}
void solve()
{int n,m;cin>>n>>m;for(int i=1;i<=m;i++){int u,v;cin>>u>>v;e[u].push_back(v);}dfs(1);for(int i=1;i<=n;i++){if(v[i] == false){cout<<"-1"<<endl;return ;}}cout<<"1"<<endl;
}signed main()
{IOSint T=1;pre_handle();
// cin>>T;while(T--) solve(); return 0;
}
最后注意使用BFS(广度优先搜索的时候元素一旦入队就将其标记为已访问)
期待后续的并查集和迪杰斯特拉算法吧~