题目大意:

题目链接:https://www.luogu.org/problemnew/show/P4997

「不围棋」是一种非常有趣的棋类游戏。

大家都知道,围棋的「气」是指一个棋子所在的联通块相邻的空格。两粒棋如果在棋盘上线段的两端就认为是相邻的,也就是在同一个连通块里。比如在图中,白子为四个独立的连通块,黑子构成一个连通块,绿色点是黑子连通块唯一的「气」:

「提子」是指将没有「气」的棋子提出棋盘,在上图中,如果白方走绿点,那么就可以将黑子全部提走。

在围棋中,我们想尽量多地占领地盘、提走对方棋子。然而,不围棋恰恰相反——不围棋是一种非常和平的游戏,双方的走子不能产生任何提子,也就是说,任何一次走子不能让棋盘上任何一个棋子所在的连通块没有气。比如,白方在上图中不能走绿点。

在你的某一步棋后,对方无棋可走,那么你就赢了。


小 F 对不围棋特别感兴趣,不过他经常输,所以他想做出一个 AI 来替他完成这局游戏。

不过造 AI 实在是太困难啦,小 F 千辛万苦写出来的 AI 被同学们的 AI 锤爆啦!

现在,他想请你帮他实现一个 AI 中一部分的功能——随机模拟,因为他相信你写的程序非常优秀,一定能优化他的 AI。

给你一个 n×nn \times nn×n 的棋盘,上面或许已经有一些棋子了,但此时局面一定是合法的,即不存在没有气的连通块;此时轮到黑棋下棋,因此棋盘上黑白棋子的数量一定是相等的。

你的任务是,依次为黑棋和白棋随意指定一个可行的走子位置,直到某一步游戏无法进行,决出胜负为止。

在正式的不围棋比赛还存在一些禁手规则。不过由于小 F 玩的是一种棋盘大小可变的新型不围棋,我们只用考虑上面提到的气的规则就好。


思路:

AC的第一道大模拟题祭orz\color{blue}\texttt{AC的第一道大模拟题祭orz}AC的第一道大模拟题祭orz
好久好久没有敲这种基本不用算法优化的大模拟题了 虽然这道题用了并查集 。
这道题2018年11月就决定敲。打来打去搞了好几次。这下总算是搞定这道题了。
Link:Link:Link:评测记录


这道题有SPJ,所以就顺序枚举黑棋白棋落子点。

先说一下大体思路吧
显然直接模拟是O(n4)O(n^4)O(n4)的。所以考虑用并查集记录连通块和气,然后再记录下上一次黑棋白棋放置的位置,直接从那个位置往下枚举即可。
先处理好初始的连通块和气。
每次判断这个落子点是否可行。如果可以,那么就把棋子落在这里,并维护上下左右棋子的气,维护联通集合。

为了简便起见,我们修改一下气的定义。一个连通块的气为∑s(x)(x∈\sum s(x)(x\in∑s(x)(x∈该连通块))),其中s(x)s(x)s(x)表示棋子xxx的上下左右有几个空格子。
这样定义的话,下图黑棋的气就是5气,而不是2气。

这样的好处是:如果我们用白棋填上最中间的位置,黑棋的气就只要减去3就可以了。否则的话还只能判断这个白棋所连接的黑棋是否是在同一个连通块内,会比较麻烦。


1.如何判断(x,y)(x,y)(x,y)能否落子

一个点可以落子,只有满足以下任意条件才行:

  • 落子后,周围另一方的棋子的气变成0
  • 落子后,这个子及所在连通块的气为0

周围另一方棋子的气是比较好判断的。只要把周围所有的对方棋子所在连通块气减1,然后判断这些连通块内是否有块没气了。只要有1个块没气,(x,y)(x,y)(x,y)就是不可以落子的。

if (map[i-1][j]==oth) sum[find(C(i-1,j))]--;
if (map[i+1][j]==oth) sum[find(C(i+1,j))]--;
if (map[i][j-1]==oth) sum[find(C(i,j-1))]--;
if (map[i][j+1]==oth) sum[find(C(i,j+1))]--;
//。。。。。。
//判断这个子及所在连通块的气是否为0
bool ok=0;
if (sum[find(C(i-1,j))] && sum[find(C(i+1,j))] && sum[find(C(i,j-1))] && sum[find(C(i,j+1))])ok=1;
if (map[i-1][j]==oth) sum[find(C(i-1,j))]++;
if (map[i+1][j]==oth) sum[find(C(i+1,j))]++;
if (map[i][j-1]==oth) sum[find(C(i,j-1))]++;
if (map[i][j+1]==oth) sum[find(C(i,j+1))]++;

判断这个子及所在连通块的气是否为0的话,先假设这个点的气为4,如果上下左右有棋子和该棋子的颜色相同,那么就加上这个连通块的气,但是同时也要减去2。因为在没有落子之前,该连通块有1气是在这个点上的,但是现在这个点落子了,这个气就没有了。并且我们一开始假设(x,y)(x,y)(x,y)的气为4,但是现在并不是上下左右都是空的,还要减去1。

然后,如果这个点的上下左右是边界或者对方棋子,气也要减少。

int s=4;
if (map[i-1][j]==ch)
{s-=2;if (!vis[find(C(i-1,j))])  //注意,每个连通块的气只能加一次,所以要判断这个连通块是否加过{       vis[find(C(i-1,j))]=1;s+=sum[find(C(i-1,j))];}
}
if (map[i+1][j]==ch)
{s-=2;if (!vis[find(C(i+1,j))]){vis[find(C(i+1,j))]=1;s+=sum[find(C(i+1,j))];}
}
if (map[i][j-1]==ch)
{s-=2;if (!vis[find(C(i,j-1))]){vis[find(C(i,j-1))]=1;s+=sum[find(C(i,j-1))];}
}
if (map[i][j+1]==ch)
{s-=2;if (!vis[find(C(i,j+1))]){vis[find(C(i,j+1))]=1;s+=sum[find(C(i,j+1))];}
}
vis[find(C(i-1,j))]=vis[find(C(i+1,j))]=vis[find(C(i,j-1))]=vis[find(C(i,j+1))]=0;  //还原
if (map[i-1][j]==oth||i==1) s--;
if (map[i+1][j]==oth||i==n) s--;
if (map[i][j-1]==oth||j==1) s--;
if (map[i][j+1]==oth||j==n) s--;  //判断边界和对方棋子bool ok=0;
if (s) ok=1;

这样,我们的checkcheckcheck函数就写好了。


2.如何合并连通块

这个应该相对简单吧。
需要解决的问题有3个。

  • 新连通块的气
  • 如何合并连通块
  • 对手连通块的气

其实不用处理新连通块的气。因为我们在checkcheckcheck函数里已经判断了落子后这个连通块的气是否大于0,而用到的变量sss就是这个连通块的气。如果这个位置可以落子,那么直接把气赋值给sss就可以了。

合并连通块其实就是最基本的并查集操作,如果上下左右是我方棋子,那么就将这个连通块和(x,y)(x,y)(x,y)合并。

处理对手的气也是非常简单的。由于我们把气的定义更改了,所以就不用判断“上和下的两个连通块是否是同一个连通块”之类的问题了。直接取上下左右的连通块的祖先,把它的气减1就可以了。

map[X][Y]=push;
if (map[X-1][Y]==map[X][Y]) father[find(C(X-1,Y))]=find(C(X,Y));
if (map[X+1][Y]==map[X][Y]) father[find(C(X+1,Y))]=find(C(X,Y));
if (map[X][Y-1]==map[X][Y]) father[find(C(X,Y-1))]=find(C(X,Y));
if (map[X][Y+1]==map[X][Y]) father[find(C(X,Y+1))]=find(C(X,Y));char oth=(push=='X'?'O':'X');
if (map[X-1][Y]==oth) sum[find(C(X-1,Y))]--;
if (map[X+1][Y]==oth) sum[find(C(X+1,Y))]--;
if (map[X][Y-1]==oth) sum[find(C(X,Y-1))]--;
if (map[X][Y+1]==oth) sum[find(C(X,Y+1))]--;printf("%d %d\n",X,Y);
if (push=='X') pushX=C(X,Y)+1;else pushO=C(X,Y)+1;
push=(push=='X'?'O':'X');

然后这道大模拟就这样切了。
AC的第一道大模拟题祭orz\color{blue}\texttt{AC的第一道大模拟题祭orz}AC的第一道大模拟题祭orz


代码:

#include <cstdio>
#include <cstring>
using namespace std;const int N=610;
int n,X,Y,pushX,pushO,father[N*N],sum[N*N];
char map[N][N],push;
bool vis[N*N];int find(int x)
{return x==father[x]?x:father[x]=find(father[x]);
}int C(int x,int y)
{if (x>n||y>n||x<1||y<1) return 0;return (x-1)*n+y;
}bool check_push(char ch)
{char oth=(ch=='X'?'O':'X');for (int k=(ch=='X'?pushX:pushO);k<=n*n;k++){int i=(k-1)/n+1;int j=(k-1)%n+1;if (map[i][j]=='.'){if (map[i-1][j]==oth) sum[find(C(i-1,j))]--;if (map[i+1][j]==oth) sum[find(C(i+1,j))]--;if (map[i][j-1]==oth) sum[find(C(i,j-1))]--;if (map[i][j+1]==oth) sum[find(C(i,j+1))]--;int s=4;if (map[i-1][j]==ch){s-=2;if (!vis[find(C(i-1,j))]){vis[find(C(i-1,j))]=1;s+=sum[find(C(i-1,j))];}}if (map[i+1][j]==ch){s-=2;if (!vis[find(C(i+1,j))]){vis[find(C(i+1,j))]=1;s+=sum[find(C(i+1,j))];}}if (map[i][j-1]==ch){s-=2;if (!vis[find(C(i,j-1))]){vis[find(C(i,j-1))]=1;s+=sum[find(C(i,j-1))];}}if (map[i][j+1]==ch){s-=2;if (!vis[find(C(i,j+1))]){vis[find(C(i,j+1))]=1;s+=sum[find(C(i,j+1))];}}vis[find(C(i-1,j))]=vis[find(C(i+1,j))]=vis[find(C(i,j-1))]=vis[find(C(i,j+1))]=0;if (map[i-1][j]==oth||i==1) s--;if (map[i+1][j]==oth||i==n) s--;if (map[i][j-1]==oth||j==1) s--;if (map[i][j+1]==oth||j==n) s--;bool ok=0;if (s && sum[find(C(i-1,j))] && sum[find(C(i+1,j))] && sum[find(C(i,j-1))] && sum[find(C(i,j+1))])ok=1;if (map[i-1][j]==oth) sum[find(C(i-1,j))]++;if (map[i+1][j]==oth) sum[find(C(i+1,j))]++;if (map[i][j-1]==oth) sum[find(C(i,j-1))]++;if (map[i][j+1]==oth) sum[find(C(i,j+1))]++;if (ok){sum[C(i,j)]=s;X=i,Y=j;return 1;}}}return 0;
}int main()
{memset(sum,0x3f3f3f3f,sizeof(sum));scanf("%d",&n);for (int i=1;i<=n;i++)for (int j=1;j<=n;j++){map[i][j]=getchar();while (map[i][j]!='X'&&map[i][j]!='O'&&map[i][j]!='.')map[i][j]=getchar();father[C(i,j)]=C(i,j);}for (int i=1;i<=n;i++)for (int j=1;j<=n;j++){sum[C(i,j)]=0;if (map[i-1][j]==map[i][j]&&map[i][j]!='.')father[find(C(i,j))]=find(C(i-1,j));if (map[i][j-1]==map[i][j]&&map[i][j]!='.')father[find(C(i,j))]=find(C(i,j-1));if (map[i-1][j]=='.') sum[find(C(i,j))]++; if (map[i+1][j]=='.') sum[find(C(i,j))]++;if (map[i][j+1]=='.') sum[find(C(i,j))]++;if (map[i][j-1]=='.') sum[find(C(i,j))]++;}push='X';pushX=pushO=1;while (check_push(push)){map[X][Y]=push;if (map[X-1][Y]==map[X][Y]) father[find(C(X-1,Y))]=find(C(X,Y));if (map[X+1][Y]==map[X][Y]) father[find(C(X+1,Y))]=find(C(X,Y));if (map[X][Y-1]==map[X][Y]) father[find(C(X,Y-1))]=find(C(X,Y));if (map[X][Y+1]==map[X][Y]) father[find(C(X,Y+1))]=find(C(X,Y));char oth=(push=='X'?'O':'X');if (map[X-1][Y]==oth) sum[find(C(X-1,Y))]--;if (map[X+1][Y]==oth) sum[find(C(X+1,Y))]--;if (map[X][Y-1]==oth) sum[find(C(X,Y-1))]--;if (map[X][Y+1]==oth) sum[find(C(X,Y+1))]--;printf("%d %d\n",X,Y);if (push=='X') pushX=C(X,Y)+1;else pushO=C(X,Y)+1;push=(push=='X'?'O':'X');}printf("-1 -1\n");return 0;
}

【洛谷P4997】不围棋【并查集】【模拟】相关推荐

  1. 洛谷 - P4997 - 不围棋 - 并查集 - 模拟

    https://www.luogu.org/problemnew/show/P4997 首先是改变气的定义,使得容易计算,这个很好理解. 然后使用并查集,因为要维护整个连通块的性质. 最后的难点在于, ...

  2. 【洛谷 P7299】 【并查集】 Dance Mooves S

    [洛谷 P7299] [并查集] Dance Mooves S 题目 解题思路 可以先求出k轮后i能到达next[i] 可以发现将会组成由很多个简单环组成的图,它们能到达的点可以共享给同一个环内的 所 ...

  3. 洛谷 - P1111 - 修复公路 - 并查集

    https://www.luogu.org/problemnew/solution/P1111 并查集的水题,水题都错了好多发. 首先并不是有环就退出,而是连通分支为1才退出,每次合并成功连通分支才会 ...

  4. 洛谷 P1967货车运输 并查集+贪心 不需要用LCA!

    题目链接 题目链接 题解 要求所有的路径中最小边长的最大值! 我们贪心的加边,依照边从大往小的方式往里添加,然后合并并查集. 每次当查询分布在两个待合并的并查集的时候,当前的边长就是这次查询的答案. ...

  5. 洛谷P1551 亲戚(并查集)

    题目链接 思路: 并查集的模板题目 关于并查集相关知识可以看此博客 AC代码 #include<iostream> #include<cstdio> #include<a ...

  6. 每日一题——洛谷 P1551 亲戚 (并查集)

    大家好,我是爬行系,今天打卡并查集相关例题 文章目录 并查集 1.概念 2.模板 例题 1.题目描述 2.AC代码 更多练习题 总结 并查集 1.概念 并查集的思想是用一个数组表示了整片森林(pare ...

  7. 解题:洛谷3402 可持久化并查集

    题面 滚下去补学考之前更一发 基于可持久化线段树实现(我不很喜欢"主席树"这个名字),注意不能路径压缩,首先这与可持久化的思想冲突(即尽量利用之前已有的部分,只修改有修改的部分,路 ...

  8. 【洛谷1892】团伙 并查集

    题意 题面说的很清楚 同noip2010关押罪犯 传送门:http://blog.csdn.net/dadatu_zhao/article/details/78806584 #include<i ...

  9. 洛谷专题训练 ——【算法1-1】模拟与高精度

    洛谷题单[算法1-1]模拟与高精度 ACM-ICPC在线模板 题单链接: [算法1-1]模拟与高精度 下面的这一坨都是洛谷题单上的东东 题单简介 恭喜大家完成了第一部分语言入门,相信大家已经可以使用 ...

最新文章

  1. 牛客寒假6-B.煤气灶
  2. linux redhat 下载_使用Vmware创建Linux(Ubuntu)系统
  3. python点击按钮浏览本地文件_Python button选取本地图片并显示的实例
  4. c++Data Member的绑定
  5. 直线回归数据 离群值_处理离群值:OLS与稳健回归
  6. HDU5923-Prediction-有继承味道的并查集
  7. python及pycharm
  8. 求绝对值 c鱼眼_初一上学期,绝对值的相关计算,提优篇
  9. HTML5大气外贸鞋子在线商城网站源码
  10. 红橙Darren视频笔记 点赞效果 动画练习
  11. qcow2 磁盘在线扩容方法
  12. Linux下idea 配置Android SDK
  13. Atitit.api参数传递的设计
  14. 录录录屏(video321)--1款亲测好用的免费高清录屏软件。你一定无法拒绝(附使用教程)
  15. 最全汇总GAN网络及其各种变体(附论文及代码实现)
  16. mini programer(1)
  17. 计算机桌面右键点击 网络,电脑桌面右键单击鼠标一直转圈如何解决
  18. 阿里云数据库怎么使用
  19. 7-12 验证哥德巴赫猜想 (10 分)
  20. Windows 10 打印机驱动无法删除和卸载的解决办法

热门文章

  1. MQ-2烟雾浓度传感器(STM32F103)
  2. prproj文件怎么安装?pr模板怎么导入使用?
  3. hiv实验室抗体筛查流程图_检验科各种流程图.doc
  4. java-core-basic
  5. CAN(FD)记录仪在新能源汽车整车控制器(VCU)、电池管理系统(BMS)、电机控制器(MCU)、发动机ECU中的应用,免去出差烦恼
  6. android 发送广播 接收广播 动态广播接收
  7. linux下rs422串口通信,RS232/RS422/RS485通信接口區別
  8. 0x1软考|网络工程师经验分享之数据通信基础(一)
  9. mysql5.7.12免安装版配置_【MySql学习笔记】免安装版5.7.12 windows配置方法
  10. 新一代深度学习框架研究