AtCoder が提供しているABC(AtCoder Beginner Contest)280 のF問題をC++とPythonで解いてみました。ABC280は、2022年12月3日21:00に実施されました。
AtCoder の紹介はこちらに、プログラミングの方針はこちらに記事があります。
F問題 Pay or Receive(Difficulty : 1819)
問題はリンク先をご覧ください。
重み付きUnionFindを使って解くことができました。AtCoder Problems による Difficulty は、1819 でした
解答案
重み付きUnionFindを使って、ABC328F問題(紹介記事)を解くことができました。この問題も重み付きUnionFindを使うとすっきりと解くことができました。
経路によってポイントが異なる場合(重みが矛盾する場合)について考えてみます。以下の図では、A→B→Cと経路を通る場合のポイントは5(2+3)です。一方、A→Cのポイントは6となります。
この場合、A→C→B→A という経路を通るとポイントが +1 になります。もし、A、B、Cの他に頂点や辺があっても、この閉路をすきなだけ回ることにより好きなだけポイントを増やすことができます。つまり、以下となります。
重みが異なる辺が見つかる
=その辺を含む連結したグラフの頂点間の移動はポイントがいくらでも増やせる。
C++ プログラム例(ABC280F)
クラス Weighted_UnionFind は、整備したコードをそのまま使いました(6ー77行目)。
矛盾した閉路を持つか保持する Bool 配列 is_inf を以下の手順で設定します。
- 矛盾した閉路を検出した場合、is_inf[根] を true に設定する。
- 統合する前の連結成分が矛盾した閉路を持つ場合、統合後の is_inf[根] を true に設定する。注)merge(a, b, c) に対して、統合した連結成分の根は、leader(a) か leader(b) のどちらかになります。
質問には、以下のように解答します。
- 連結していなければ、”nan” を出力する。
- 矛盾した閉路を持つ場合、”inf” を出力する。
- 上記以外の場合、diff(a, b) の値を出力する。
最終的なC++プログラムは、以下となります。
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
class Weighted_UnionFind {
private:
int n;
std::vector<int> par;
std::vector<ll> diff_weight;
public:
Weighted_UnionFind(int _n) {
n = _n;
par.resize(n, -1);
diff_weight.resize(n, 0);
}
int leader(int x) {
if (par[x] < 0) {
return x;
} else {
int r = leader(par[x]);
diff_weight[x] += diff_weight[par[x]];
return par[x] = r;
}
}
ll weight(int x) {
leader(x);
return diff_weight[x];
}
ll diff(int x, int y) {
return weight(y) - weight(x);
}
void merge(int x, int y, ll w) {
w += weight(x);
w -= weight(y);
x = leader(x);
y = leader(y);
if (x != y) {
if (-par[x] < -par[y]) {
std::swap(x, y);
w = -w;
}
par[x] += par[y];
par[y] = x;
diff_weight[y] = w;
}
}
bool same(int x, int y) {
return leader(x) == leader(y);
}
int size(int x) {
return -par[leader(x)];
}
std::vector<std::vector<int>> groups() {
std::vector<std::vector<int>> member(n);
for (int i = 0; i < n; ++i) {
member[leader(i)].push_back(i);
}
std::vector<std::vector<int>> result;
for (int i = 0; i < n; ++i) {
if (!member[i].empty()) {
result.push_back(member[i]);
}
}
return result;
}
};
int main()
{
int n, m, q;
cin >> n >> m >> q;
Weighted_UnionFind uf(n + 1);
vector<bool> is_inf(n + 1, false);
for (int i = 0; i < m; ++i) {
int a, b;
ll c;
cin >> a >> b >> c;
if (uf.same(a, b)) {
if (uf.diff(a, b) != c) {
is_inf[uf.leader(a)] = true;
}
} else {
if (is_inf[uf.leader(a)]) {
is_inf[uf.leader(b)] = true;
}
if (is_inf[uf.leader(b)]) {
is_inf[uf.leader(a)] = true;
}
uf.merge(a, b, c);
}
}
for (int i = 0; i < q; ++i) {
int x, y;
cin >> x >> y;
if (!uf.same(x, y)) {
cout << "nan" << endl;
} else if (is_inf[uf.leader(x)]) {
cout << "inf" << endl;
} else {
cout << uf.diff(x, y) << endl;
}
}
return 0;
}
AC(Accepted=正しいプログラム)と判定されました。
Python の重み付きUnionFindについて
Python は、重み付きUnionFind を標準ライブラリとしては用意していません。このブログの Python プログラムは、なるべく言語標準のライブラリで実装したいと考えています。このような事情で、この記事では、Python に移植したプログラムの紹介を省略します。
最後に
コンテストABC280参加時のメモに「問題をみることもなく放置」とありました。重み付きUnionFindを前提にすれば、なんとか理解できました。約1年の実力の向上でしょうか。
引き続き ABC の問題を紹介していきます。