心路历程

根据题面的描述,我们面临的问题无非是,每次将色块更新成什么颜色。又因为是从左上角开始更新,所以我的有了第一个想法。

将左上角的色块命名为“原色块”。

对于每个色块,定义4中状态:

0-
不属于原色块势力,和原色块势力不邻接,未没有进行任何操作;

1-
不属于原色块势力,和原色块势力邻接,尚不知道会不会被同化(因为还不知道下一步染什么色);

2-
属于原色块势力,和非原色块势力邻接,是上一批被同化的色块;

3-
属于原色块势力,和非原色块势力不邻接**;

我们可以形象地依次把这四种状态称作“未加入”,“待审核”,“新成员”,“老成员”

以下图为例(颜色是色块,数字是状态):

第一步比较特殊,因为需要求出最初的新队员。(之后就只需要处理新成员和待审核就可以了,因为新成员处于邻接位置,需要承担审核工作。而老成员和未加入不需要做任何工作)

首先原色块自身是新成员,然后跑一遍bfs,求出所有与原色块连通且与原色块同色的色块,将它们都标记为新成员。

之后的每一步,首先统计与新成员邻接且未加入的色块,将它们标记为待审核,并统计每种颜色出现的次数。

出现次数最多的颜色就是我们即将更新的颜色。得到这个之后新成员的工作就做完了,新成员就可以标记为老成员了。

再次遍历所有待审核的色块,若颜色和即将更新的颜色相同,则标记为新成员;否则重新标记为未加入。

将所有新成员、老成员的颜色更新。

每次都需要判断是否更新完整个图。

这个算法逻辑上是行得通的,但是太慢了,因为要反复地遍历各种状态,导致大量无用的重复运算。

正解

标算:IDA*

先不谈IDA*。我们回到这道题,从模块化编程的角度出发,想想这个程序要实现什么功能。

首先我们有更新色块的操作。对于更新色块的函数,我们需要3个形参:当前色块的横坐标、纵坐标、我们即将更新的颜色。我们仍然需要一个打标记的数组:若当前位置的颜色和即将更新的颜色相同则标记为1,反之则标记为2.所以这个函数的主要功能是给需要更新颜色的色块打标记,还没有进行上色操作。

void update_flags(int i,int j,int color)//color是即将更新的颜色
{
if(board[i][j]!=color)
{
flags[i][j]=2;//和即将更新的颜色不同,打上标记2
return ;
}
flags[i][j]=1;//和即将更新的颜色相同,打上标记1
for(int k=0;k<4;k++)
{
//dir[][]用于枚举4个方向
int x=i+dir[k][0];
int y=j+dir[k][1];
if(x<0||y<0||x>=siz||y>=siz||flags[x][y]) continue;
update_flags(x,y,color);//递归更新尚未打标记的色块
}
}

然后考虑具体的上色操作。最外层循环遍历6种颜色,然后遍历全图。若当前遍历到的位置是不同颜色且正好等于要搜索的颜色,则记录存在这种颜色,并进行\(update_flags\).若全图遍历完后发现不存在这种颜色,则继续遍历下一个颜色。如果更新了棋盘,就递归搜索,成功了就返回。注意:\(flags\)数组在这个过程中可能会被改变,为了后续的使用,我们需要在操作前拷贝\(flags\)到\(tmp\)数组,操作结束后再拷贝回去。

接下来再考虑使用IDA.IDA可以理解为以迭代加深DFS的搜索框架为基础,把原来简单的深度限制加强为:若当前深度+未来估计步数>深度限制,则立即从当前分支回溯

具体到本题,我们的估价函数就应该是:如果颜色数量大于剩余步数,则行不通。所以我们还需要一个计算剩余颜色数量的函数。

int color_cnt()
{
int ret=0;
int s=0;
for(int i=0;i<siz;i++)
for(int j=0;j<siz;j++)
if(flags[i][j]!=1)
s|=(1<<board[i][j]);
while(s)//检查还有几种颜色
{
ret+=s&1;
s>>=1;
}
return ret;
}

并且,我们要在主函数中枚举每一种最大限定深度。

最终代码呈现如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=15;
int siz;
int board[N][N];
int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int flags[N][N];
int max_depth;
void update_flags(int i,int j,int color)//color是即将更新的颜色
{
if(board[i][j]!=color)
{
flags[i][j]=2;//和即将更新的颜色不同,打上标记2
return ;
}
flags[i][j]=1;//和即将更新的颜色相同,打上标记1
for(int k=0;k<4;k++)
{
//dir[][]用于枚举4个方向
int x=i+dir[k][0];
int y=j+dir[k][1];
if(x<0||y<0||x>=siz||y>=siz||flags[x][y]) continue;
update_flags(x,y,color);//递归更新尚未打标记的色块
}
}
int color_cnt()
{
int ret=0;
int s=0;
for(int i=0;i<siz;i++)
for(int j=0;j<siz;j++)
if(flags[i][j]!=1)
s|=(1<<board[i][j]);
while(s)//检查还有几种颜色
{
ret+=s&1;
s>>=1;
}
return ret;
}
int dfs(int depth)
{
//如果颜色数量大于剩余步数,则行不通(估价函数)
if(color_cnt()>max_depth-depth)
return 0;
//如果当前深度刚好等于最大深度,说明已经找完了
if(depth==max_depth)
return 1;
int tmp[N][N];
int color_exist;
memcpy(tmp,flags,sizeof(flags));
for(int color=0;color<6;color++)
{
color_exist=0;
for(int i=0;i<siz;i++)
for(int j=0;j<siz;j++)//当前位置是不同颜色且正好等于要搜索的颜色
if(flags[i][j]==2&&board[i][j]==color)
{
color_exist=1;
update_flags(i,j,color);
}
//颜色不存在,继续搜索
if(!color_exist) continue;
//如果更新了棋盘,就递归搜索,成功则返回
if(dfs(depth+1)) return 1;
memcpy(flags,tmp,sizeof(flags));
}
return 0;
}
int main()
{
while(scanf("%d",&siz)&&siz)
{
for(int i=0;i<siz;i++)
for(int j=0;j<siz;j++)
scanf("%d",&board[i][j]);
//限定最大深度
for(max_depth=0;;max_depth++)
{
memset(flags,0,sizeof(flags));
update_flags(0,0,board[0][0]);
if(dfs(0)) break;
}
printf("%d\n",max_depth);
}
return 0;
}

参考

flood_it 方法记录的更多相关文章

  1. EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态

    本文目录 查看实体当前.原始和数据库值:DbEntityEntry 查看实体的某个属性值:GetValue<TValue>方法 拷贝DbPropertyValues到实体:ToObject ...

  2. 64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录

    64位 SQL Server2008链接访问Oracle 过程汇总解决方法记录 经过几天不停的网上找资料,实验,终于联通了. 环境:系统:win 2008 ,SqlServer2008 R2, 连接O ...

  3. js实用方法记录-js动态加载css、js脚本文件

    js实用方法记录-动态加载css/js 附送一个加载iframe,h5打开app代码 1. 动态加载js文件到head标签并执行回调 方法调用:dynamicLoadJs('http://www.yi ...

  4. js实用方法记录-简单cookie操作

    js实用方法记录-简单cookie操作 设置cookie:setCookie(名称,值,保存时间,保存域); 获取cookie:setCookie(名称); 移除cookie:setCookie(名称 ...

  5. js实用方法记录-指不定哪天就会用到的js方法

    js实用方法记录-指不定哪天就会用到的js方法 常用或者不常用都有 判断是否在微信浏览器中 测试代码:isWeiXin()==false /** * 是否在微信中 */ function isWeix ...

  6. Java给各个方法记录执行时间

    Java给各个方法记录执行时间 long startTime = System.currentTimeMillis();...//要测试时间的方法LoggerFactory.getLogger(Bas ...

  7. make menuconfig error 解决方法记录

    新建的一个虚拟机,发现make menuconfig 后会出错,查了一下是缺少一些库. 这个错误已经错了两次了,我不希望第三次出现了还想不起来,所以特此记录. # 错误信息: make[2]: *** ...

  8. 简单一键CENTOS6 安装PPTP VPN方法记录

    申明:我们使用PPTP VPN仅仅只能用在查阅资料等正规渠道,不要用在不良用途上.方法收集于网上,这里我用在搬瓦工VPS(VPS方案直达),采用的是CENTOS6 64位系统.我们需要预先将VPS服务 ...

  9. ASP.NET页面优化性能提升方法记录

    今天与大家分享:一种优化页面执行速度的方法.采用这个方法,可以使用页面的执行速度获得[8倍]的提升效果. 为了让您对优化的效果有个直观的了解,我准备了下面的测试结果截图: 测试环境:1. Window ...

随机推荐

  1. css基础02

    熟练快捷键!方便,要多练!  css复合选择器 不会选孙子,有一个儿子和另一个儿子的孩子(也是孙子)同名了,但子选择器子选择儿子,同名的孙子不选.和后代选择器有一点不一样的. " ,&quo ...

  2. Java学习 (六)基础篇 类型转换

    类型转换 由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换 字节大小(容量)-> 低--------------------------------------------- ...

  3. 只要9.9元!零基础学习MySQL

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 导语 经过一段时间的筹备和整理,万里数据库<零基础学习MySQL>课程正式在腾讯课堂上线了. 课程地址:htt ...

  4. LuoguP5390 [Cnoi2019]数学作业(数论)

    转进制,然后发现贡献只有\(1_{(2)}\),取奇数个的子集方案是\(2^{n-1}\) #include <iostream> #include <cstdio> #inc ...

  5. Redis 10 位图

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 概述 Redi ...

  6. mybatis 10: 动态sql --- part2

    < foreach >标签 作用 用来进行循环遍历,完成循环条件的查询,批量删除,批量增加,批量更新 用法 循环查询 + 批量删除 + 批量增加 + 批量更新 UsersMapper.ja ...

  7. WebStorm 配置 Vue3 的文件模板

    WebStorm 默认的 Vue 模板不是 setup 函数(组合式 API)模板,而是 Options API 模板.在设置中搜索 File and Code Templates 编辑创建 vue ...

  8. HandInDevil 的头发 (分 块)

    题面 H a n d I n D e v i l \rm HandInDevil HandInDevil 的头发很油,因此随时有跳蚤跳上 H a n d I n D e v i l \rm HandI ...

  9. QPanter 绘画

    Qpainter 绘图 1 绘图事件 void paintEvent(QPaintEvent *event) 2 声明一个画家对象 QPainter painter(this) this  指定绘图设 ...

  10. 快速生成组件语法模板的插件:Auto Close Tag

    好家伙, 这是一个"标签闭合"插件 Auto Close Tag的安装: 来到VScode的拓展 安装后, 在其中输入一个左标签符号"<",随后会出现提示 ...