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因为这边的点比较多,可能会出现爆栈的情况,剩下的就靠读者了,可以使用笔者的代码去对拍

posted @   banyanrong  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示