【JZOJ 6520】Fence Planning
闲话:
$ Upd 4.04 $
让我们
-
哀悼抗击新冠肺炎疫情斗争牺牲烈士和逝世同胞
-
致敬在这次斗争中默默付出的平凡英雄们
-
纪念那些被 404 的声音
题目大意:
有 \(n\) 个点在一个棋盘上,一个点可以和多个点连一条边,其中有 \(m\) 条边,连成的点可以作一个小组,现在用一个长方形把任意一个小组框起来,那长方形的周长最小多长。
正文:
本题思路很简单,在求连通块的过程中,可以分别维护同一连通块中横纵坐标的最大值和最小值。本题可以用并查集和 DFS 做。
方法 1:
并查集。把难点讲细一点,在每次询问点时,判断点是否是小组(即连通块)边缘。
代码:
for(int i = 1; i <= n; i++) //询问
check(i); //判断
for(int i = 1; i <= n; i++)
if(fa[i] == i)
ans = min(ans, 2 * ((b[i][1].x - b[i][2].x) + (b[i][1].y - b[i][2].y))); //更新答案(其中b[i][1].x,b[i][2].x,b[i][1].y,b[i][2].y分别表示i点所在的连通块中最大的x坐标、最小的x坐标、最大的y坐标、最小的y坐标)
其中判断的函数是:
inline void check(int x)
{
b[i][1].x = max(b[i][1].x, a[x].x);
b[i][2].x = min(b[i][2].x, a[x].x);
b[i][1].y = max(b[i][1].y, a[x].y);
b[i][2].y = min(b[i][2].y, a[x].y);
}
方法二:
用 dfs 暴力搜。dfs 的方法其实就是暴力,枚举到 \(i\) 点时,将它所在的连通块里的其他点搜了,同时找到连通块边缘的点。
代码:
for (int i = 1; i <= n; ++i)
if(!vis[i])
{
build(a[i].x, a[i].y); //初始化
dfs(i); // 搜索
ans = min(ans, 2 * ((maxx - minx) + (maxy - miny))); //更新答案
}
其中搜索和初始化的函数是:
inline void build(int a, int b)
{
maxx = minx = a;
miny = maxy = b;
}
inline void opt(int a, int b)
{
maxx = max(maxx, a);
minx = min(minx, a);
maxy = max(maxy, b);
miny = min(miny, b);
}
void dfs(int x)
{
vis[x] = 1;
opt(a[x].x, a[x].y);
for (int i = head[x]; i; i = e[i].next)
if(!vis[e[i].to]) dfs(e[i].to);
}