Strorkisのブログ

信憑性については悪しからず

ABC065D

https://atcoder.jp/contests/abc065/tasks/arc076_b

座標 (a, b) (c, d)を繋ぐ辺の重みが (min(|a-c|,|b-d|)である時、N個の座標 (x_i, y_i)における最小全域木の重みを求めよという問題です。


最小全域木は名前しか知らなかったので、解説を読み、最小全域木で検索することによって、何とか解けました。

(コードは読みにくいですが)


方針としては解説の通りです(説明が面倒くさかった模様)

まず、x軸・y軸を基準に、それぞれソートします。

ここでは、各座標の添え字を残して置きたかったので、添え字を並べ替えています。

  int n; cin >> n;
  int x[n], y[n];
  vector<int> v(n), w(n);
  for (int i = 0; i < n; i++) {
    cin >> x[i] >> y[i];
    v[i] = w[i] = i;
  }

  sort(v.begin(), v.end(), [&](int a, int b) {
    return x[a] < x[b];
  });
  sort(w.begin(), w.end(), [&](int a, int b) {
    return y[a] < y[b];
  });


次に、各辺を重みでソートします。

こちらも、各辺の添え字を残して置きたかったので、添え字を並び替えています。

 fは、辺の重みを計算する関数です(名前が良くない)

/* typedef pair<int, int> Edge; */

  auto f = [&](Edge e) {
    int ex = abs(x[e.first] - x[e.second]);
    int ey = abs(y[e.first] - y[e.second]);
    return min(ex, ey);
  };

  int en = n * 2 - 2;
  Edge edges[en];
  int cost[en];
  vector<int> u(en);
  for (int i = 0; i < en/2; i++) {
    edges[i] = Edge(v[i], v[i+1]);
    cost[i] = f(edges[i]);
    u[i] = i;
  }
  for (int i = en/2; i < en; i++) {
    edges[i] = Edge(w[i-en/2], w[i+1-en/2]);
    cost[i] = f(edges[i]);
    u[i] = i;
  }

  sort(u.begin(), u.end(), [&](int a, int b) {
    return cost[a] < cost[b];
  });


最後に、クラスカル法を用いて、最小全域木の重みを計算します。

具体的には、重みが小さい順に辺を取り出し、その辺の座標のどちらかが初登場であれば、辺を追加するという処理をしています(Union Findを用いることで高速に行うことができる)

Union Findのコードは、ほぼ検索して出てきたものそのままなので、省略します。

(ansの型を無意識にlong longにしてしまったのですが、明らかにintで良かった)

  long long ans = 0;
  UnionFind uf(en);
  for (int i : u) {
    int p = edges[i].first;
    int q = edges[i].second;
    if (!uf.same(p, q)) {
      uf.unite(p, q);
      ans += cost[i];
    }
  }
  cout << ans << endl;


今回は、最小全域木とUnion Findという知識を増やすことができたので、嬉しかったです。

では。