1 条题解
-
0
自动搬运
来自洛谷,原作者为

Rigel
苍山负雪,明烛天南。|| 高二现役 OIer。搬运于
2025-08-24 21:44:00,当前版本为作者最后更新于2024-05-23 17:58:40,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题意
在一个二维平面上,分布着水()、大陆()与小岛()。现要求一条长度最短的环线路线(起点与终点在同一位置),它应满足:
-
这条路线经过的每一个格子只能是水()。
-
这条环线路线的内部应包括整个大陆()。
-
这条环线路线的内部不应包括任一小岛()。
此外,我们规定这条路线可以多次经过同一个格子。也就是说,路线可以重叠。求这条路线的长度,即这条路线经过格子的个数(数据保证有解)。
例如下图,这条红色的路线是不合法的(路线内部包括了一个小岛 )。

如下图,这条蓝色的路线是合法的(图中深蓝色表示路径重叠部分,阴影部分表示环线路线的内部)。

思路
染色
简而言之,这道题的本质就是染色。
染色在本题中体现为将一些水()染色成为大陆()。染色完毕后,只需求出整个大陆的周长即可。于是,如何染色成为了本题的重难点。
对于每一个水格子()是否应该被染色,我们从以这个格子为中心的 方格来考虑。为简单起见,我们考虑如下图所示定义以水格子 为中心的 方格:

当 不是水()时,直接跳过该点;当 是水时,分以下几种情况讨论:
- 当格子 、、、 中有大于等于三个格子是大陆()时,直接将 染色为 ,因为最短的路不可能走到这里。例如下图,格子 、、 为 ,因此格子 也被染成 。

- 当格子 、、、 中恰好有两个格子是大陆()时,且这两个 分别位于格子 的两侧,则这个格子暂时不填。例如下图,格子 、 为 。若将格子 填上,则格子 、、 向外延伸的水连通块与格子 、 向外延伸的水连通块有可能会被互相隔绝。

在下图的情况下,中间的格子是不能被填上的。因为如果填上了,会把里面的 与外界隔绝,是不合法的。

在下图的情况下,中间的格子之后会被自下而上地重新搜到而填充上色,因为下面的格子之后会被染色。

因此,我们在将任一格子填充之后,必须重新判断这个格子周围一圈(八个)格子的情况并讨论其是否需要填充上色。
- 当格子 、、、 中恰好有两个格子是大陆()时,且这两个 格子的顶点相邻(例如格子 、 或格子 、),则分以下两种情况讨论:
(1)当格子 填上后,会把这个 方格中剩余的水格子分割成独立的两块,则这个点暂时不填,理由同第二种情况。例如下图,顶点相邻的格子 、 为 ,若将格子 填上,则可能会把格子 、 向外延伸的水连通块与格子 、 向外延伸的水连通块相互隔绝。

需要注意的是,隔绝水格子的不只是大陆(),也有可能是小岛()。
(2)当格子 填上后,不会把这个 方格中剩余的水格子分割成独立的两块,则这个点直接填上。例如下图,顶点相邻的格子 、 为 ,我们将格子 直接填上。

这种类型的格子直接填上的原因是:填上之后只有好处没有坏处。如左下图,填上之后路径长度没有改变;如右下图,填上之后会导致右边两个粉色的格子之后也被填上,最终路径长度缩短。换句话说,填这种类型的格子对路径有截弯取直的作用。

- 当格子 、、、 中只有小于等于一个格子是大陆()时,这个格子暂时不填,同理,如有需要之后会重新搜回来。
按这种方式处理完所有格子之后,我们直接求一遍所得大陆的周长即可得出答案。
如下是样例染色前与染色后的对比。
................... ................... .....A............. .....A..x.......... ..x..A.....AAAA.... .....A.....A..A.... .....AAAAAAAA.A.... ........A.....A.... .xx...AAA...x.A.... ......A............ ...AAAAAAAAAAAAA... ...................................... ................... ....AAA............ ....AAA.x.......... ..x.AAA...AAAAA.... ....AAAAAAAAAAA.... ....AAAAAAAAAAA.... ....AAAAAAA...A.... .xx.AAAAAAA.x.A.... ....AAAAAAA........ ...AAAAAAAAAAAAA... ...................求大陆周长
我们采用常规思路:从第一次出现的大陆()的上面一格为起点开始走出第一步,紧贴大陆顺时针走一圈,并统计走过的格子个数。
假设上一步的方向为向右,则这一步应优先向下,因为我们需要紧贴大陆走一圈;如果无法往下,则应尝试向右;如果还是不行,则应向上(我们规定第一步的上一步方向为向右)。
如下图,分别给出了当上一步(红色)为向右时,这一步(蓝色)分别向右、向上、向下时的情况。

同理,我们可得:若上一步的方向为 (其中 满足该方向所代表的射线绕原点顺时针旋转 之后恰与 轴正半轴重合),则下一步应优先向 (右转),若不行则向 (直行),再不行则向 (左转),其中角度按模 计。

按此方法一直走,并记录经过的格子个数,直到走回起点。
代码实现 & 完整代码
由于这道题考察的主要是思维,且每个人的代码习惯不一,我会给出我的完整代码和注释,而不是每一步的具体实现方法。
#include<bits/stdc++.h> #define int long long #define maxn 1010 using namespace std; int n,m,ans; int d[10][2]={{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}}; int f[5][2]={{0,1},{1,0},{0,-1},{-1,0}}; char c[maxn][maxn]; inline int read(){ int ret=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+(ch&15),ch=getchar(); return ret*f; } inline char GC(){ char ch=getchar(); while(ch!='.'&&ch!='A'&&ch!='x')ch=getchar(); return ch; } bool check(int x,int y){ // 判断一个点是否在地图范围之内 return (x>=1&&y>=1&&x<=n&&y<=m); } void color(int x,int y){ // 染色函数 if(c[x][y]!='.')return; // 若当前格子不是水,直接跳过 int cnt_A=0,cnt_x=0; // 统计该格子周围 A 的数量与 x 的数量 for(int i=0;i<=3;i++){ int xx=x+f[i][0],yy=y+f[i][1]; if(!check(xx,yy))continue; cnt_A+=(c[xx][yy]=='A'); } for(int i=0;i<=7;i++){ int xx=x+d[i][0],yy=y+d[i][1]; if(!check(xx,yy))continue; cnt_x+=(c[xx][yy]=='x'); } int chk=0; for(int i=0;i<=3;i++){ if(c[x+f[i][0]][y+f[i][1]]=='A'&&c[x+f[(i+1)%4][0]][y+f[(i+1)%4][1]]=='A'&&c[x+f[(i+2)%4][0]][y+f[(i+2)%4][1]]=='.'&&c[x+f[(i+3)%4][0]][y+f[(i+3)%4][1]]=='.'){ // 情况 3. chk=i+1; break; } } if((chk==1&&c[x-1][y-1]!='.')||(chk==2&&c[x-1][y+1]!='.')||(chk==3&&c[x+1][y+1]!='.')||(chk==4&&c[x+1][y-1]!='.'))chk=0; // 情况 3.(1) if((chk&&(!cnt_x))||cnt_A>=3){ // 情况 1. 或 3.(2) c[x][y]='A'; for(int i=0;i<=7;i++){ int xx=x+d[i][0],yy=y+d[i][1]; if(!check(xx,yy))continue; color(xx,yy); // 染色之后要重新判断周围格子的情况 } } // 情况 2. 与 4. 均不满足染色条件 } void calc(int x,int y){ // 计算大陆周长 int nx=x,ny=y,opt=0; // opt 为上一步方向编号 do{ for(int i=5;i>=3;i--){ // 按优先级尝试走每一个方向(i 取 5 至 3 是为了避免下标出现负数) int xx=nx+f[(opt+i)%4][0],yy=ny+f[(opt+i)%4][1]; if(c[xx][yy]=='.'){ nx=xx,ny=yy; opt=(opt+i)%4; // 更新方向编号 break; } } ans++; // 记录经过的格子总数 }while(!(nx==x&&ny==y)); } signed main(){ n=read(),m=read(); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)c[i][j]=GC(); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)color(i,j); // 尝试对每一个格子做染色操作 int chk=1; for(int i=1;i<=n&&chk;i++){ for(int j=1;j<=m&&chk;j++){ if(c[i][j]=='A'){ calc(i-1,j); // 从第一次出现的 A 的上面一格开始计算大陆周长 chk=0; } } } printf("%lld\n",ans); return 0; } -
- 1
信息
- ID
- 2041
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者