提到圖論中的搜索問題,首先想到的也就是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(廣度優先搜索的時候元素一旦入隊就將其標記為已訪問)
期待后續的并查集和迪杰斯特拉算法吧~