寒训3F Built?

寒训3F Built?

题意

平面上有 N N N 个城市。第 i i i 个城市的坐标为 ( x i , y i ) (x_i,y_i) (xi,yi) 。同一个坐标上可能有多个城市。在坐标为 ( a , b ) (a,b) (a,b) 的城市和坐标为 ( c , d ) (c,d) (c,d) 的城市间建造一条道路需要 m i n ( ∣ a − c ∣ , ∣ b − d ∣ ) min(|a-c|,|b-d|) min(ac,bd) 円。只能在城市与城市间建造道路。
要使任意两个城市之间有直接或间接道路相连,最少需要多少円?

数据范围
2 ≤ N ≤ 1 0 5 2 \leq N \leq 10^5 2N105
0 ≤ x i , y i ≤ 1 0 9 0 \leq x_i,y_i \leq 10^9 0xi,yi109
• 输入全为整数

思路

很容易就能想到这题就是在问最小生成树的长度(其中两个点所连的边的边权为 m i n ( ∣ 横坐标之差 ∣ , ∣ 纵坐标之差 ∣ ) min(|横坐标之差|,|纵坐标之差|) min(横坐标之差,纵坐标之差)),但是数据范围又提醒我们如果所有边都去遍历一遍势必会超时,所以我们只能选择其中的一些边。

如果把横纵坐标拆开来看呢?考虑平面内横坐标互不相同的三点A、B、C(横坐标依次递增),若只考虑横坐标则连接AB和BC的两条边的边权势必会小于等于AC的边权——此时可以说在横坐标上AC这条边被淘汰了;也就是说,在横坐标方面我们只需要考虑相邻两个点所连的一条边即可(纵坐标也同理),这样一个点最多会连4条边,最多 4 × 1 0 5 4×10^5 4×105条边,是可以接受的。

最后用Kruskal算法来得到最小生成树,问题解决。

代码

#include <iostream>
#include <algorithm>
#define MAXN 100005
using namespace std;
int n;
long long ans;

struct P {
    int x, y;
    int num;    //由于排序会把下标打乱(这样边的两端就乱了),因此我们需要一个不变编号
    void read() {
        cin >> x >> y;
    }
} p[MAXN];

struct Edge {
    int u, v;
    long long w;
} eg[MAXN << 2];        //一个点最多和四个点相连

int U;            //每个点的第一条边
int f[MAXN];    //父节点

void init() {
    for (int i = 1; i <= n; ++i) {
        f[i] = i;
    }
}

int find_f(int x) {
    return f[x] == x ? x : f[x] = find_f(f[x]);
}

bool xcmp(P p1, P p2) {
    return p1.x < p2.x;
}

bool ycmp(P p1, P p2) {
    return p1.y < p2.y;
}

bool wcmp(Edge e1, Edge e2) {
    return e1.w < e2.w;
}

void link(P p1, P p2) {        //增加一条无向边
    U ++;
    eg[U].u = p1.num;
    eg[U].v = p2.num;
    eg[U].w = min(abs(p2.x - p1.x), abs(p2.y - p1.y));
}

int main() {

    cin >> n;
    init();
    for (int i = 1; i <= n; ++i) {
        p[i].read();
        p[i].num = i;
    }
    sort(p + 1, p + 1 + n, xcmp);
    for (int i = 1; i < n; ++i) {
        link(p[i], p[i + 1]);
    }
    sort(p + 1, p + 1 + n, ycmp);
    for (int i = 1; i < n; ++i) {
        link(p[i], p[i + 1]);
    }
    sort(eg + 1, eg + 1 + U, wcmp);
    for (int i = 1; i <= U; ++i) {
        int u = eg[i].u;
        int v = eg[i].v;
        int uu = find_f(u);
        int vv = find_f(v);
        if (uu == vv)
            continue;        //说明它们已经可以连接了,无需再去连
        ans += eg[i].w;
        f[uu] = vv;
    }
    cout << ans;

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值