洛谷P1332血色先锋队的 “天灾扩散小剧场”

11 阅读7分钟

血色先锋队天灾扩散小剧场多.png 要看视频效果在这里: www.douyin.com/video/76040…

【场景:血色十字军临时营地,小血(新兵)蹲在地上画格子吐槽,算法师啃着黑面包路过,程序猿抱着笔记本凑过来】

小血(抓狂挠头,戳着地上的泥土格子):

“救命啊!巫妖王的天灾军团扩散得比食堂抢面包还快!要算那几个领主啥时候被感染,用单源 BFS 一个个跑,我怕是要写到退休!”

算法师(叼着面包含糊不清,蹲下来指格子图):

“急啥?你这是老掉牙的‘孤狼打野’思路!现在咱们用多源 BFS,就像天灾不止一个传染源(指浅绿色格子),是好几个瘟疫点同时往外蹦僵尸!”

小血(眼睛发亮):

“同时蹦?那咋保证每个格子最早被感染的时间啊?”

算法师(指着代码里的队列初始化):

“你看这俩浅绿色的感染源(1,1)(5,4),它们自带‘感染时间 0’,我们直接把所有感染源一次性塞进队列,然后像开了‘天灾扩散挂’一样,四个方向同时蔓延!每个邻居格子的感染时间,就是当前格子的时间 + 1。

BFS 是按‘层’扩散的,第一次摸到某个格子的时间,肯定是最早的 —— 就像你和队友一起包抄,谁先到谁算,绝无插队!”

小血(恍然大悟,拍大腿):

“哦!原来不是一个僵尸慢慢爬,是一群僵尸‘团战 rush’!那黄色的领主格子(比如(2,4)(3,3)(5,3)),格子里的红色数字就是它们‘中招’的时间?”

程序猿(把笔记本怼到两人面前,屏幕上是动态渲染的格子):

“对咯!我用C++ 精灵库把这个过程可视化啦!你看,浅绿色感染源先亮,然后一层一层往外染红色数字,黄色领主的位置一出现数字,就是它们被感染的最早时间!

P1332 血色先锋队多源BFS可视化运行结果.png

要是没这可视化,光看数组里的干巴巴数字,哪能直观感受到‘多源同时扩散’的爽感?精灵库直接把格子绘制、颜色填充、动态动画都搞定了,不用我们自己吭哧吭哧写绘图逻辑!”

算法师(拍程序猿肩膀):

“还是你会玩!这精灵库把抽象的算法变成了‘看得见的天灾游行’,调试的时候看动图比看日志爽一百倍!”

小血(盯着屏幕拍手):

“哇!原来算法不是冷冰冰的代码,是能看见的僵尸大游行!以后再遇到多起点找最短路径的题,我就知道先把所有起点塞队列里,一起跑 BFS!”

程序猿(得意笑):

“没错!而且C+精灵库还能调颜色、写数字、做动画,把复杂的算法逻辑变成‘干将莫邪都能看懂的剧场’,咱再也不用对着数组秃头啦!”

算法师(举杯状,面包当酒杯):

“来!敬多源BFS的‘团战效率’,也敬C++精灵库的‘可视化魔法’!以后算感染时间,咱们都是‘天灾观测大师’!”


代码献上;

#include "sprites.h"  //包含C++精灵库
#include <queue>
#include <algorithm>
using namespace std;
Screen sc{"作者:李兴球"}; 
Sprite rocket;      //建立角色叫rocket,画格子,写数字等用途
int n=25,m=20,sidelen=25; //行,列,边长,示例里的5行,4列,边长设为100
int a = 3,b=4; //3个感染源,4个邻主,这里只进行演示,数据不需要输入,直接硬写。
int level[502][502];     //每个节点的层级,就最早感染时间
int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};  //4个方向
queue< pair<int,int> > q;
bool visited[502][502];   
vector<pair<int,int>> lord;  //保存几个领主的坐标 
vector<pair<int,int>> source; //保存几个感染源的坐标
pair<int,int> convert_cors(int row,int col){  //坐标转换(0,0)左上角转换为真实坐标
     //srcx,srcy是单位坐标,sidelen是格子边长
     int x = sidelen*((n/2-row)) - (1-n%2)*sidelen/2;
     int y = (1-m%2)*sidelen/2-sidelen*((m/2-col));
     return {y,x};
}
void draw_grid(int n,int m,int step){    //画格子图
    //行数,列数,格子边长,以原点画格子图
    int width = m*step,height=n*step;
    rocket.penup().go(-width/2,height/2).pd();
    for(int i=0;i<n;i++)rocket.fd(width).bk(width).addy(-step);
    rocket.penup().go(step-width/2,height/2).rt(90).pd();    
    for(int i=1;i<m;i++)rocket.fd(height).bk(height).addx(step);
    rocket.fd(height).right(90).fd(width);
}
bool is_in_vector(pair<int,int> &cur,vector<pair<int,int> > &v){ //cur是否在v向量中
    vector<pair<int,int>>::iterator it = find(v.begin(),v.end(),cur);
    return it!=v.end();   
}

void render_grid(pair<int,int> &cur){  //根据坐标类型不同渲染不同的背景色
   rocket.go(convert_cors(cur.first,cur.second));
   if(is_in_vector(cur,lord)) rocket.fill("yellow");
   else if (is_in_vector(cur,source)) rocket.fill("light green");
   rocket.write(to_string(level[cur.first][cur.second]),16);
   rocket.wait(0.01);
}
int main(){  //三位领主以黄色标记,每个节点感染时间就是它们的层级
   g_screen->bgcolor("black"); 
   rocket.speed(0).color("gray").hide();  
   draw_grid(n,m,sidelen); rocket.penup().color("pink"); 
   //在第一行上面写数字序号
   for(int col=1;col<=m;col++)
     rocket.go(convert_cors(-1,col-1)).write(to_string(col),16); 
   //在第一列左边写数字序号
   for(int row=1;row<=n;row++)
     rocket.go(convert_cors(row-1,-1)).write(to_string(row),16);
     
   rocket.color("red");  //格子里的层级数字用红色来写
   visited[0][0]=true; visited[14][3]=true;  visited[20][19]=true; //标记感染源节点已被访问
   q.push({0,0});   q.push({14,3}); q.push({20,19}); //三个感染源
   source.push_back({0,0});   source.push_back({14,3});  //保存感染源
   source.push_back({20,19});
   //保存领主坐标
   lord.push_back({12,2}); lord.push_back({4,2}); lord.push_back({1,3});
   lord.push_back({20,8}); lord.push_back({24,12}); lord.push_back({10,13});
   //开始进行多源BFS,即多个感染源扩散,这里才是核心代码
   while(!q.empty()){
      pair<int,int> cur = q.front();q.pop();
      render_grid(cur);//可视化渲染当前格子
      for(int i=0;i<4;i++){
	    int nr = cur.first + dirs[i][0];  //邻居行坐标
	    int nc = cur.second + dirs[i][1];//邻居列坐标
	    if(nr<0 || nr>=n || nc<0 || nc>=m)continue; //超出范围的忽略
	    if(visited[nr][nc]==true)continue;  //已访问过的节点忽略
	    visited[nr][nc]=true; 
	    q.push({nr,nc});   //入队
	    level[nr][nc] = level[cur.first][cur.second] + 1;	    
	  }
   }
   for(pair<int,int> p:lord){
      int row = p.first,col = p.second;
      cout << level[row][col] << endl;
   }   
   rocket.done(0);
   return 0;
}

/*
代码解释:
这段代码实现了一个基于C++精灵库的简单游戏场景,模拟了“血色先锋队”对抗天灾军团的情景。主要功能包括:

初始化与设置:

使用sprites.h提供的API来创建和管理屏幕上的元素。
设置了屏幕大小、角色属性以及基本的游戏参数如网格尺寸、感染源位置等。
绘制网格系统:

draw_grid函数负责在整个屏幕上画出规则的方格网,方便后续定位各个单位的位置。
然后在该函数后面完成了对行列编号的文字标注工作。
坐标转换机制:

convert_cors方法能够把内部使用的二维数组索引转换成适合绘图的实际像素坐标系下的点位信息。这对于正确放置各种实体非常重要。
状态检测辅助工具:

is_in_vector是一个通用的小工具函数,用来快速判断某个特定位置是否属于某一类对象集合之中(比如是否是感染源或者领主所在地)。
核心渲染逻辑:

render_grid根据传入的不同类型参数来决定如何给相应的格子着色——如果是感染源就涂成浅绿色;若是领主则标记为黄色;其他情况下仅显示该处的数字值表示距离最近的感染源还有多少步远。此外还会调用等待命令使得整个过程更加直观易懂。
广度优先搜索算法实现:

主循环采用了经典的BFS策略来进行扩散传播模拟。从多个起点同时出发向四周探索未知区域,并逐步记录下到达每一个可达点的最少步数。这种处理方式确保了即使存在多个起始点也能高效准确地计算出全局最优解。
结果展示环节:

最后遍历所有已知的领主位置,打印出它们各自对应的被感染所需时间作为最终输出内容之一。这也验证了前面所做的一切计算都是正确的并且达到了预期目标。
*/