AcWing 1125 牛的旅行


一、理清概念

牧区: 对应一个点
牧区之间的距离:实际上是两点之间的 最短路。 不要理解成欧几里得距离。只有 直接连接 的时候,才可以计算欧几里得距离。
牧场: 一个连通块
牧场直径: 一个牧场的直径是这个牧场所有的牧区(点)之间 距离 的最大值。 说的绕一点就是 最短路的最大值
使用一条边连接两个牧场,使得合成的一个新的牧场的直径最小。意思是加入一条边之后,使得新的牧场的所有点对之间 最短路 的最大值 最小

由于最后连接两个牧场的路线可以在两个连通块间的点枚举添加,目标是直径最小,那么取出添加的最小值。

但这个最小值未必就是答案,原因是就算你这个最小,但问题是原来连通块中的直径可能还比这个值大,那么根据定义,原来的直径才算是现在连通后真正的直径,还需要\(PK\)一下最大值。太绕了,终于说清楚了...

二、代码实现

#include 
using namespace std;
#define x first
#define y second
typedef pair PII;
const int N = 152;
const int INF = 0x3f3f3f3f;

PII q[N];       //每个牧区的坐标
char g[N][N];   //地图
double d[N][N]; //每两个牧区之间的距离
double maxd[N]; //距离牧区i最远的最短距离是多少
//计算两个牧区之间的距离
double get_dist(PII a, PII b) {
    int x = a.x - b.x, y = a.y - b.y;
    return sqrt(x * x + y * y);
}

int main() {
    //牧区小,牧场大
    int n; //牧区数
    cin >> n;
    for (int i = 0; i < n; i++) cin >> q[i].x >> q[i].y; //牧区的坐标
    //一个对称邻接矩阵,图的样子,g是一个二维char数组,一次读入一行
    for (int i = 0; i < n; i++) cin >> g[i];

    //遍历行与列,计算出每两个牧区之间的距离
    //距离只在同一连通块中存在,不同的连通块间的距离是INF
    //自己与自己的距离是0
    //如果两个牧区相连,那么距离的计算办法是sqrt((x1-x2)^2+(y1-y2)^2)
    //欧几里得距离
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i == j)
                d[i][j] = 0;
            else if (g[i][j] == '1')
                d[i][j] = get_dist(q[i], q[j]);
            else //注意:由于d数组是一个double类型,不能用memset(0x3f)进行初始化正无穷
                d[i][j] = INF;
        }
    }
    // Floyd算法模板
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);

    double res1 = 0; //未建设两个连通块之间线路前,已知的牧区间最大距离是多少
    double res2 = INF;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) //求i到离i(最短距离)最远点的距离
            /*注意一下这个INF/2的用法,很经典,松弛操作后不见的是INF
            只有小于INF/2才是真正被更新的了有效距离*/
            if (d[i][j] < INF / 2) maxd[i] = max(maxd[i], d[i][j]);
        // res1保持原来(两个牧场中)任意两个牧区间的最大距离(直径)
        res1 = max(res1, maxd[i]);
    }
    //连线操作,更新新牧场直径
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (d[i][j] > INF / 2) //如果i,j不在同一个连通块内
                //连接原来不在同一连通块中的两个点后,可以取得的最小直径
                res2 = min(res2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));
    // PK一下
    printf("%.6lf\n", max(res1, res2));
    return 0;
}