You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 1144. 连接格点

一、题目描述

有一个 mn 列的点阵,相邻两点可以相连。

一条纵向的连线花费一个单位,一条横向的连线花费两个单位。

某些点之间已经有连线了,试问 至少 还需要花费多少个单位才能使所有的点全部连通。

输入格式 第一行输入两个正整数 mn

以下若干行每行四个正整数 x_1,y_1,x_2,y_2,表示第 x_1 行第 y_1 列的点和第 x_2 行第 y_2 列的点已经有连线。

输入保证|x_1x_2|+|y_1y_2|=1

输出格式 输出使得连通所有点还需要的最小花费。

数据范围 1≤m,n≤1000 0≤已经存在的连线数≤10000

输入样例

2 2
1 1 2 1

输出样例

3

二、题目解析

n*m的点阵,则点数是n*m个,由于边权都是正数,所以当连接n*m-1条边时,才能都连通,然后边权和最小,其实是在求一个 最小生成树 问题。

:如果是边权可能为 负数 ,则不是最小生成树问题,可以想象一个极端的例子,比如5个点,边权都是负数, 那么要想使边权和最小,可以想尽办法把所有边都连上,就会最小,这时就不是最小生成树问题了。

本题与上一题很类似,图中都是有两类边,处理方法也很类似,考察的其实是想象能力。图中的点是n*m的点阵,连接纵向的两点花费1个单位,连接横向的两点消耗2个单位。我们其实并不需要显式的用一个结构体数组或者邻接矩阵去存储这个图,因为点阵是极具规律性的,自左而右,自上而下点n*m个点依次编号为1 n*m。在读取已有的连线时,获取这两个点的编号,连接这两点即可。后面要考虑的是如何连接剩下的点?

既然连接纵向边的消耗小,我们自然是先枚举纵向边再枚举横向边,按顺序尝试加入到并查集中。如何枚举纵向边,点阵中的纵向边是点i和点i + m的连线,其中i1(n - 1) * m。横向边则是ii + 1的连线,当遍历到编号为m的倍数的点时跳过即可。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 1000 * 1000 + 10, M = N << 1;
int n, m;

// 二维转一维的办法,坐标从(1,1)开始
int get(int x, int y) {
    // m为列宽
    return (x - 1) * m + y;
}
struct Edge {
    int a, b, c;
    const bool operator<(const Edge &t) const {
        return c < t.c;
    }
} edge[M];
int el;

// 并查集
int p[N];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
// 先连1的边再连2的边
void create_edges() {
    for (int i = 1; i <= (n - 1) * m; i++) // 前n-1行的每个点都可以向下引一条边权为1的边
        edge[el++] = {i, i + m, 1};        // i -> i+m,边权为1

    for (int i = 1; i <= n * m; i++) { // 向右引边,注意最后一列不能向右引边
        if (i % m == 0) continue;      // 最后一列放过
        edge[el++] = {i, i + 1, 2};    // i->i+1,边权为2
    }
    // 因为加进去就是按边权由小到大录入的,所以不用再排序了
}
int main() {
    cin >> n >> m;
    // 建边
    create_edges();

    // 并查集初始化
    for (int i = 1; i <= n * m; i++) p[i] = i;

    int x1, y1, x2, y2;
    // 利用二维转一维办法,将(x,y)映射成节点编号
    // 某些点之间已经有连线了=> 标识这些点已在并查集中
    while (cin >> x1 >> y1 >> x2 >> y2) {
        int a = get(x1, y1), b = get(x2, y2);
        p[find(a)] = find(b);
    }

    int res = 0; // 用Kruskal算法即可
    for (int i = 0; i < el; i++) {
        int a = find(edge[i].a), b = find(edge[i].b), c = edge[i].c;
        if (a != b) p[a] = b, res += c;
    }
    cout << res << endl;
    return 0;
}