Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

問題: Maximum Spanning Tree Queries #26

Merged
merged 13 commits into from
Aug 5, 2024
Merged
76 changes: 76 additions & 0 deletions _common/dsu.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#ifndef ATCODER_DSU_HPP
#define ATCODER_DSU_HPP 1

#include <algorithm>
#include <cassert>
#include <vector>

namespace atcoder {

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
public:
dsu() : _n(0) {}
explicit dsu(int n) : _n(n), parent_or_size(n, -1) {}

int merge(int a, int b) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);
int x = leader(a), y = leader(b);
if (x == y) return x;
if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y);
parent_or_size[x] += parent_or_size[y];
parent_or_size[y] = x;
return x;
}

bool same(int a, int b) {
assert(0 <= a && a < _n);
assert(0 <= b && b < _n);
return leader(a) == leader(b);
}

int leader(int a) {
assert(0 <= a && a < _n);
if (parent_or_size[a] < 0) return a;
return parent_or_size[a] = leader(parent_or_size[a]);
}

int size(int a) {
assert(0 <= a && a < _n);
return -parent_or_size[leader(a)];
}

std::vector<std::vector<int>> groups() {
std::vector<int> leader_buf(_n), group_size(_n);
for (int i = 0; i < _n; i++) {
leader_buf[i] = leader(i);
group_size[leader_buf[i]]++;
}
std::vector<std::vector<int>> result(_n);
for (int i = 0; i < _n; i++) {
result[i].reserve(group_size[i]);
}
for (int i = 0; i < _n; i++) {
result[leader_buf[i]].push_back(i);
}
result.erase(
std::remove_if(result.begin(), result.end(),
[&](const std::vector<int>& v) { return v.empty(); }),
result.end());
return result;
}

private:
int _n;
// root node: -1 * component size
// otherwise: parent
std::vector<int> parent_or_size;
};

} // namespace atcoder

#endif // ATCODER_DSU_HPP
7 changes: 7 additions & 0 deletions maximum-spanning-tree-queries/PROBLEM
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8; mode: python -*-

problem(
time_limit=5.0,
id="f",
title="Maximum Spanning Tree Queries",
)
169 changes: 169 additions & 0 deletions maximum-spanning-tree-queries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Maximum Spanning Tree Queries

実行時間制限: 5 sec

## 問題文

$N$ 頂点 $M$ 辺からなる、重み付き無向グラフ $G = (V, E)$ が与えられます。 グラフは連結ですが、単純とは限りません。
$i (1 \le i \le M)$ 番目の辺は $u_i, v_i$ を双方向に結んでおり、重みは $c_i$ です。

$Q$ 個のクエリが与えられます。
$j (1 \le j \le Q)$ 番目のクエリでは、重み $w_j$ の辺 $e_j = \\{ x_j, y_j \\}$ が与えられます。
各クエリについて、次の問題を解いてください。

- グラフ $(V, E \cup e_j)$ の最大全域木 (辺の重みの和が最大となる全域木) の辺の重みの総和を求めよ。
- すなわち、グラフ $G$ に $e_j$ を追加したときの最大全域木の辺の重みの総和を求めよ。

ただし、クエリの前後でグラフ $G$ は変化しないものとします。

## 制約

- $2 \le N \le 2 \times 10^5$
- $N - 1 \le M \le 2 \times 10^5$
- $1 \le Q \le 2 \times 10^5$
- $G$ は連結
- $1 \le c_i, w_j \le 10^9$
- $1 \le u_i, v_i, x_j, y_j \le N$
- $u_i \ne v_i, x_j \ne y_j$
- 入力はすべて整数である

### 部分点

この問題はいくつかの小課題に分けられ、その小課題の全てのテストケースに正解した場合に、「この小課題に正解した」とみなされます。
提出したソースコードの得点は、正解した小課題の点数の合計となります。

1. (10 点) $N \le 100, M \le 100, Q \le 1000$
2. (20 点) $N \le 100, Q \le 1000$
3. (70 点) 追加の制約はない

## 入力

入力は以下の形式で標準入力から与えられる。

$N$ $M$<br />
$u_1$ $v_1$ $c_1$ <br />
$u_2$ $v_2$ $c_2$ <br />
... <br />
$u_M$ $v_M$ $c_M$ <br />
$Q$ <br />
$x_1$ $y_1$ $w_1$ <br />
$x_2$ $y_2$ $w_2$ <br />
... <br />
$x_Q$ $y_Q$ $w_Q$ <br />

## 出力

$Q$ 行出力せよ。
$j$ 行目には、$j$ 番目のクエリに対する答えを出力せよ。

## サンプル

### 1

```text
4 5
1 2 6
1 3 3
1 4 1
2 3 5
2 4 4
2
3 4 10
1 2 10
```

```text
21
19
```

初期状態では、以下の図のようになっています。

![Sample 1 - 1](https://a01sa01to.com/images/cms/2024/08/maximum-cup-2024-img/mstq/sample1-1.svg)

1 番目のクエリでは、 $3, 4$ 間に重み $10$ の辺 (下図緑の辺) を追加します。
これにより得られる最大全域木は以下の赤い辺で構成されます。
辺の重みの総和は $6 + 5 + 10 = 21$ です。

![Sample 1 - 2](https://a01sa01to.com/images/cms/2024/08/maximum-cup-2024-img/mstq/sample1-2.svg)

2 番目のクエリでは、 $1, 2$ 間に重み $10$ の辺を追加します。
グラフは単純とは限らないことに注意してください。
また、クエリの前後でグラフは変化しないことに注意してください。

![Sample 1 - 3](https://a01sa01to.com/images/cms/2024/08/maximum-cup-2024-img/mstq/sample1-3.svg)

### 2

```text
2 1
1 2 3
5
1 2 1
1 2 2
1 2 3
1 2 4
1 2 5
```

```text
3
3
3
4
5
```

### 3

```text
7 10
1 6 15
6 2 62
7 4 83
2 1 98
6 3 74
5 4 45
2 7 8
1 5 36
5 7 54
2 5 40
5
3 2 83
1 7 50
2 4 35
4 5 45
3 5 54
```

```text
432
421
411
411
425
```

## 解法

### 部分点 1

クエリごとに MST を $O(M \log M + N \alpha(N))$ などで求めることができる。

### 部分点 2

初期状態の MST を求め、使わない辺はその時点で削除。
すると $M = N - 1$ とできて、これもまたクエリごとに MST を求めることができる。

### 満点

LCA の要領で、クエリが与えられたときに $x_j$-$y_j$ パス上の辺の重みの最小値を求める。
その最小値より $w_j$ が大きければ採用する。

### wa-initial-minimal

初期状態の MST を求めて、使わない辺はその時点で削除する。
使っている辺のうち、重みの最小値を保持しておく。
クエリが与えられたら、その最小値より大きければ採用する。

最小値が $x_j$-$y_j$ パスにある場合は通るが、そうでないときはダメ。
3 changes: 3 additions & 0 deletions maximum-spanning-tree-queries/sol-cpp-ac/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
cxx_solution(src='main.cc', flags=["-std=c++2a", "-O2", "-I../../../_common"])
expected_score(100)
103 changes: 103 additions & 0 deletions maximum-spanning-tree-queries/sol-cpp-ac/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); ++i)

#include "dsu.hpp"

int main() {
int n, m;
cin >> n >> m;
vector<tuple<int, int, int>> edges(m);
for (auto&& [a, b, c] : edges) cin >> a >> b >> c;

ll ans = 0;
{
sort(edges.begin(), edges.end(), [](auto a, auto b) {
return get<2>(a) > get<2>(b);
});
atcoder::dsu d(n);
vector<tuple<int, int, int>> usedEdges(0);
for (auto [a, b, c] : edges) {
if (!d.same(a - 1, b - 1)) {
d.merge(a - 1, b - 1);
ans += c;
usedEdges.push_back({ a - 1, b - 1, c });
}
}
edges = usedEdges;
}

vector Graph(n, vector<pair<int, int>>(0));
for (auto [a, b, c] : edges) {
Graph[a].push_back({ b, c });
Graph[b].push_back({ a, c });
}

// 0 を根にする
const int log2n = bit_width((unsigned int) n);
vector<int> depth(n, -1);
vector par(n, vector<int>(log2n, -1));
vector minim(n, vector<int>(log2n, 2e9));

// initialize
{
queue<int> q;
q.push(0);
depth[0] = 0;
while (!q.empty()) {
int v = q.front();
q.pop();
for (auto [u, c] : Graph[v]) {
if (depth[u] == -1) {
depth[u] = depth[v] + 1;
par[u][0] = v;
minim[u][0] = c;
q.push(u);
}
}
}
rep(k, log2n - 1) rep(v, n) {
if (par[v][k] != -1) {
par[v][k + 1] = par[par[v][k]][k];
minim[v][k + 1] = min(minim[v][k], minim[par[v][k]][k]);
}
}
}

auto Query = [&](int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
int res = 2e9;
int t = depth[x] - depth[y];
for (int k = log2n - 1; k >= 0; --k) {
if (t & (1 << k)) {
res = min(res, minim[x][k]);
x = par[x][k];
}
}
if (x == y) return res;
for (int k = log2n - 1; k >= 0; --k) {
if (par[x][k] != par[y][k]) {
res = min({ res, minim[x][k], minim[y][k] });
x = par[x][k], y = par[y][k];
}
}
return min({ res, minim[x][0], minim[y][0] });
};

int q;
cin >> q;
while (q--) {
int x, y, z;
cin >> x >> y >> z;
int res = Query(x - 1, y - 1);
if (res >= z) {
// z は不採用
cout << ans << endl;
}
else {
cout << ans - res + z << endl;
}
}
return 0;
}
3 changes: 3 additions & 0 deletions maximum-spanning-tree-queries/sol-cpp-partial1/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
cxx_solution(src='main.cc', flags=["-std=c++2a", "-O2", "-I../../../_common"], challenge_cases=["*_task2.in", "*_all.in"])
expected_score(10)
Loading