6.4 图
图(graph)描述的是一些个体之间的关系。与线性表和二叉树不同的是,这些个体间不是前驱后继的顺序关系,也不是祖先后代的层次关系,而是错综复杂的网状关系
对于线性表(链表来说)各种节点之间的关系是前驱后继
对于树来说,各种结点之间的关系是祖先后代的层次关系
对于图来说,各种结点之间是错综复杂的网状关系
Oil_Deposits题解
点击查看笔者代码
#include<iostream>
#include<sstream>
#include<queue>
using namespace std;
const int maxl = 100 + 5;
int oil[maxl][maxl];
struct point{
int x, y;
point(int x = 0, int y = 0) : x(x), y(y) {}
};
int main() {
int x, y;
while(scanf("%d%d", &x, &y) && x && y) {
char line[105];
for(int i = 0; i < x; i++) {
scanf("%s", line);
stringstream ss(line);
for(int j = 0; j < y; j++) {
char ch;
ss >> ch;
if(ch == '*') oil[i][j] = 0;
else oil[i][j] = maxl*maxl;
}
}
int cnt = 0;
for(int i = 0; i < x; i++) {
for(int j = 0; j < y; j++) {
if(oil[i][j] && oil[i][j]==maxl*maxl) {
oil[i][j] = ++cnt;
queue<point> q;
q.push(point(i, j));
while(!q.empty()){
int dis[8][2] = {1,-1,1,1,1,0,0,1,0,-1,-1,1,-1,0,-1,-1};
point temp = q.front();
q.pop();
for(int k = 0; k < 8; k++)
if(temp.x+dis[k][0] >= 0 && temp.x+dis[k][0] < x && temp.y+dis[k][1] >= 0 && temp.y + dis[k][1] < y && oil[temp.x+dis[k][0]][temp.y+dis[k][1]] == maxl*maxl) {
oil[temp.x+dis[k][0]][temp.y+dis[k][1]] = oil[temp.x][temp.y];
q.push(point(temp.x+dis[k][0], temp.y+dis[k][1]));
}
}
}
}
}
cout << cnt << endl;
}
return 0;
}
笔者看到这题首先是非常自然的想到模仿树的BFS中队列的使用来实现不漏,而不重复直接通过判断该块是否被遍历过就可以了,最后使用cnt来计数
这边作者引入了图的DFS和BFS遍历,图的DFS更容易编写,同时一般用DFS来找连通块,即从每个对应的'@'格子除法,递归遍历它周围的“@”格子,每次访问时候就给他写上一个连通分量编号,这样就可以在访问之前检查它是否已经有了编号,从而避免多次访问同一个格子,作者的代码如下
点击查看代码
#include<cstdio>
#include<cstring>
const int maxn = 100 + 5;
char pic[maxn][maxn];
int m, n, idx[maxn][maxn];
void dfs(int r, int c, int id) {
if(r < 0 || r >= m || c < 0 || c >= n) return;//“出界”的格子//非常妙的一种想法,统一在边界条件排除非法情况
if(idx[r][c] > 0 || pic[r][c] != '@') return; //不是“@”或者已经访问过的格子//上述就是边界条件,保证路的唯一性
idx[r][c] = id;
for(int dr = -1; dr <= 1; dr++)
for(int dc = -1; dc <= 1; dc++)
if(dr != 0 || dc != 0) dfs(r+dr, c+dc, id); //这边是对八连通的一种表示,同时也可以采用数组,二者的本意是一样的
}
int main() {
while(scanf("%d%d", &m, &n) == 2 && m && n) {
for(int i = 0; i < m; i++) scanf("%s", pic[i]);//常规的读入操作
memset(idx, 0, sizeof(idx));//对于标记数组idx的清零
int cnt = 0;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(idx[i][j] == 0 && pic[i][j] == '@') dfs(i, j, ++cnt);//对于每个格子遍历,如果该格子符合要求并且没有被标记过那么就开始递归填充
printf("%d\n", cnt);//cnt计数,最后输出答案就可以了
}
return 0;
}
上面的代码中作者用了一个二重循环来找当前格子的相邻8个格子,也可以用常量数组或者写8条DFS调用,可以根据自己的喜好选用。
这道题作者的算法有一个独特的名字:种子填充(floodfill)
图也有DFS遍历和BFS遍历,其中前者用递归实现,后者用队列实现
求多维数组连通块的过程也成为种子填充(floodfill)
种子填充很形象的表示了深搜的过程,总的来说这道题所告诉我们的方法时求解连通块即种子填充问题,我们可以采用图的DFS遍历来实现
Ancient_Messages题解
点击查看笔者代码
#include<iostream>
#include<cstring>
#include<cctype>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
const int H = 200+5, W = 50+5;
int n, m, graph[H][W*4], dis[9][2] = {0,1,0,-1,1,0,-1,0,0,0,1,1,1,-1,-1,1,-1,-1};
struct Node{
int x, y;
Node(int x = 0, int y = 0) : x(x), y(y) {}
};
void paint(int pr, int pc, int val) {
queue<Node> q;
q.push(Node(pr, pc));
while(!q.empty()) {
int r = q.front().x, c = q.front().y;
q.pop();
for(int k = 0; k < 9; k++) {
int i = dis[k][0], j = dis[k][1];
if(!(r+i < 0 || r+i >= n || c+j < 0 || c+j >= 4*m) && !graph[r+i][c+j]) {
graph[r+i][c+j] = val;
q.push(Node(r+i, c+j));
}
}
}
}
int getVal(int r, int c, vector<int> &list) {
if(r < 0 || r >= n || c < 0 || c >= 4*m || graph[r][c] == 0) return 0;
if(graph[r][c] != 1) {
if(graph[r][c] == 2) return 0;
for(int i = 0; i < list.size(); i++)
if(graph[r][c] == list[i]) return 0;
list.push_back(graph[r][c]);
return 0;
}
graph[r][c] = 0;
for(int i = 0; i < 4; i++) getVal(r+dis[i][0], c+dis[i][1], list);
return list.size();
}
int main(){
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int kase = 0;
while(scanf("%d%d", &n, &m)==2 && n && m) {
memset(graph, 0, sizeof(graph));
char line[W];
for(int i = 0; i < n; i++) {
cin >> line;
for(int j = 0; j < m; j++) {
int temp = 0, num = 0, buf[4];
memset(buf, 0, sizeof(buf));
(isdigit(line[j])) ? num = line[j]-'0' : num = line[j]-'a'+10;
while(num) {
buf[temp++] = num%2;
num /= 2;
}
for(int k = 0; k < 4; k++) graph[i][j*4+k] = buf[3-k];
}
}
int cnt = 3;
for(int i = 0; i < n; i++) {
if(!graph[i][0]) paint(i, 0, 2);
if(!graph[i][4*m-1]) paint(i, 4*m-1, 2);
}
for(int i = 0; i < 4*m; i++) {
if(!graph[0][i]) paint(0, i, 2);
if(!graph[n-1][i]) paint(n-1, i, 2);
}
for(int i = 1; i < n-1; i++)
for(int j = 1; j < m*4-1; j++)
if(!graph[i][j]) paint(i, j, cnt++);
vector<char> output;
// AJDSWK 135402
for(int i = 0; i < n; i++)
for(int j = 0; j < m*4; j++)
if(graph[i][j] == 1) {
vector<int> list;
switch(getVal(i, j, list)) {
case 0: output.push_back('W'); break;
case 1: output.push_back('A'); break;
case 2: output.push_back('K'); break;
case 3: output.push_back('J'); break;
case 4: output.push_back('S'); break;
case 5: output.push_back('D'); break;
default: break;
}
}
sort(output.begin(), output.end());
cout << "Case " << ++kase << ": ";
for(int i = 0; i < output.size(); i++) cout << output[i];
cout << endl;
}
return 0;
}
好吧,对于这种大型模拟题目,笔者槽点是大开的,码到崩溃是常有之事情,这题很明显要用到前面的种子填充思想,同时注意作者在这边的提示是:
随意拉伸但不能拉断,表示的含义就是拓扑变换,也就是图形需要拓扑等价,也就是说遇到这类题目我们不能拘泥于细节,而应该从全局考虑,找到一个易于计算,同时随意拉伸时还不会改变的特征量,很明显,因为每个符号多是一个四连块,而中间的空洞是不一样的,因此可以通过空洞数来进行判断
这个思路有见过类似的题目将会很自然的联想到,那么这边就只剩下代码白那些的问题了,首先笔者推荐还是在读取的外面添上一层0,这样可以将最外面的0八连通起来,否则就需要像笔者一样判断半天,是否是图形里面的空白,其次是这边最好使用BFS,如果使用DFS因为这边的点比较多,可能会出现爆栈的情况,剩下的就靠读者了,可以使用笔者的代码去对拍
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)