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

Commit

Permalink
問題: Maximum Profits by Toll (#29)
Browse files Browse the repository at this point in the history
* create maximum-profits-by-toll

* update gitignore

* fix pack.sh

* update input format

* create partial1 solution

* fix problem constraints

* create partial2 solution

* create ac solution

* create judge, TESTSET

* create validator

* create testcases

* create cpy solutions

* fix?

* fix challenge_cases

* update: use pypy

* test

* upd requirements.txt

* h_j -> t_j
  • Loading branch information
a01sa01to authored Aug 9, 2024
1 parent dfe40eb commit 6c0cb67
Show file tree
Hide file tree
Showing 67 changed files with 5,861,431 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/workflows/rime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pypy -m ensurepip
pypy -mpip install -U pip wheel
pypy -mpip install -r requirements.txt
- name: Enumerate Problems
run: |
problems=$(ls -d */ | grep -v _common | tr -d '/' | tr ' ' '\n' | sort -bdfu)
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Thumbs.db
*.out
target
.venv
*.zip
7 changes: 7 additions & 0 deletions maximum-profits-by-toll/PROBLEM
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8; mode: python -*-

problem(
time_limit=2.0,
id="i",
title="Maximum Profits by Toll",
)
196 changes: 196 additions & 0 deletions maximum-profits-by-toll/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Maximum Profits by Toll

実行時間制限: 2 sec

## 問題文

彩国には、 $1$ から $N$ の番号が付いた $N$ 個の町があり、町を結ぶ $M$ 本の道があります。
$j (1 \le j \le M)$ 番目の道は、町 $u_j$ から町 $v_j$ へ向かう一方通行の道路です。
それぞれの道について、今年は $t_j$ 人通るというデータが求まっています。

国王である Asa さんは、それぞれの道に対して、非負整数の通行料 $f_j$ 円を設定することで、利益を生み出そうと考えました。
彩国の住民は 1 度決めたら行動を変えないため、 $t_j$ は $f_j$ によって変化しません。
しかし、それぞれの町長は、その町に入るための通行料が高くなってしまうと観光客が減ってしまうと恐れ、 Asa さんに以下の条件を満たすように求めました。

- すべての町 $i (1 \le i \le N)$ について、 $X$ をほかの町から町 $i$ へ向かう道路の集合、 $Y$ を町 $i$ からほかの町へ向かう道路の集合としたとき、以下の条件を満たす。
$$\sum_{j \in X} f_j \le c_i + \sum_{j \in Y} f_j$$
- なお、 $c_i$ は入力で与えられる非負整数である

Asa さんは、町長から求められた条件を満たすように通行料を設定することで、利益 $\sum_{j=1}^{M} t_j f_j$ を最大化したいと考えています。
Asa さんが得られる利益の最大値を求めてください。

## 制約

- $2 \le N \le 2 \times 10^5$
- $1 \le M \le \min(N (N-1), 2 \times 10^5)$
- $1 \le u_j, v_j \le N$
- $u_j \ne v_j$
- $j \ne j' \Rightarrow (u_j, v_j) \ne (u_{j'}, v_{j'})$
- $1 \le t_j \le 10^4$
- $0 \le c_i \le 10^4$
- 入力はすべて整数である

<!-- t_j, c_i はもっと大きくしたかったが 10^5 でもオーバーフローしてしまう... -->
<!-- この制約で hand01 は 1999990000000000000 (2 x 10^18) 64bit 符号付整数型に収まる -->

### 部分点

1. (5 点) すべての $i$ について $c_i = 0$
2. (10 点) 与えられるグラフは有向木である。つまり、以下の 2 条件を同時に満たす。
- $M = N - 1$ である
- 根となる町 $r$ を適切に選ぶことで、 $r$ からすべての町に到達可能である
3. (85 点) 追加の制約はない

## 入力

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

$N$ $M$ <br />
$c_1$ $c_2$ ... $c_N$ <br />
$u_1$ $v_1$ $t_1$ <br />
$u_2$ $v_2$ $t_2$ <br />
... <br />
$u_M$ $v_M$ $t_M$ <br />

## 出力

Asa さんが得られる利益の最大値を出力せよ。
利益をいくらでも大きくすることができる場合は、 `-1` を出力せよ。

## サンプル

### 1

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

```text
9
```

与えられるグラフは、以下のグラフです。

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

以下のように通行料を設定することで、条件を満たしながら利益を最大化することができます。

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

この入力は、小課題 3 の制約を満たします。

### 2

```text
3 2
1 1 1
1 2 1
2 3 1
```

```text
3
```

この入力は、小課題 2, 3 の制約を満たします。

### 3

```text
6 6
0 0 0 0 0 1
1 2 1
1 3 1
2 4 10000
3 5 1
4 6 1
5 6 100
```

```text
10002
```

この入力は、小課題 3 の制約を満たします。

## 解法

閉路を含む場合は `-1` となる。
ある 1 つの閉路に注目し、そこに含まれる辺については等しい任意の値、それ以外の辺は $f_j = 0$ とすることにより、条件を満たしながらいくらでも大きくできる。

以下、グラフは DAG であることを仮定する。

### 部分点 1

$c_i = 0$ の場合。

このとき、葉に注目すると、出辺がないので
$$\sum_{j \in X} f_j \le c_i + \sum_{j \in Y} f_j \Rightarrow \sum_{j \in X} f_j \le 0$$
となる。
一方、 $f_j$ は非負整数なので、 $f_j = 0$ とするしかない。

葉の親についてみると、 $\sum_{j \in Y} f_j = 0$ となるので $\sum_{j \in X} f_j \le 0$ となる。
同様の議論を親に繰り返し適用することにより、結局すべての辺について $f_j = 0$ とすることになる。

よって答えは `0`

### 部分点 2

有向木となる場合。
このとき、根を除く任意の町 $i$ について、入辺はただ 1 本である。

$t_j$ は非負なので、できるだけ $f_j$ を大きくしていったほうが良い。
葉から根に向かって、 $f_j = c_i + \sum_{k \in Y} t_k$ としていけばよい。
根については $-c_i - \sum_{j \in Y} t_j$ となるが、明らかに 0 以下となるので条件を満たす。

### 満点

基本的には部分点 2 のようにやればよいが、入辺に対する分配に困る。

$A$ をグラフの接続行列とする。つまり、以下の定義。ただし $x$ は任意の町。

$$
A_{ij} = \begin{cases}
-1 & \text{if } e_j = (i, x) \\
1 & \text{if } e_j = (x, i) \\
0 & \text{otherwise}
\end{cases}
$$

線形計画問題で表現してみると、以下のようになる。

$$
\begin{aligned}
\text{maximize} & \quad \mathbb{h}^T \mathbb{f} \\
\text{subject to} & \quad A \mathbb{f} \le \mathbb{c} \\
& \quad \mathbb{f} \ge \mathbb{0}
\end{aligned}
$$

$A$ は Totally Unimodular であり $\mathbb{c}$ は整数ベクトルなので、最適解において $\mathbb{f}$ が整数ベクトルとなるものが存在する。

双対をとると、以下のようになる。

$$
\begin{aligned}
\text{minimize} & \quad \mathbb{c}^T \mathbb{x} \\
\text{subject to} & \quad A^T \mathbb{x} \ge \mathbb{h} \\
& \quad \mathbb{x} \ge \mathbb{0}
\end{aligned}
$$

これもまた、最適解において $\mathbb{x}$ が整数ベクトルとなるものが存在する。
文章で書くと、以下のようになる。

- 町 $i$ について、非負整数 $x_i$ を定める
- ただし、各辺 $e_j = (u_j, v_j)$ について、 $x_v \ge v_u + t_j$ を満たす必要がある
- $\sum_{i=1}^{N} c_i x_i$ を最小化せよ

これはトポロジカル順に、一番先頭の町を 0 として、条件を満たすように決めていけば解くことができる。
3 changes: 3 additions & 0 deletions maximum-profits-by-toll/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"])
expected_score(100)
51 changes: 51 additions & 0 deletions maximum-profits-by-toll/sol-cpp-ac/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); ++i)

int main() {
int n, m;
cin >> n >> m;
vector<int> c(n);
rep(i, n) cin >> c[i];
vector Graph(n, vector<pair<int, int>>(0));
vector revGraph(n, vector<pair<int, int>>(0));
rep(i, m) {
int u, v, t;
cin >> u >> v >> t;
Graph[u - 1].push_back({ v - 1, t });
revGraph[v - 1].push_back({ u - 1, t });
}

vector<int> topological(0);
vector<int> indeg(n, 0);
rep(u, n) for (auto [v, _] : Graph[u]) indeg[v]++;
queue<int> q;
rep(u, n) if (indeg[u] == 0) q.push(u);
while (!q.empty()) {
int u = q.front();
q.pop();
topological.push_back(u);
for (auto [v, _] : Graph[u])
if (--indeg[v] == 0) q.push(v);
}
bool isDag = true;
rep(u, n) if (indeg[u] > 0) isDag = false;

if (!isDag) {
cout << -1 << endl;
return 0;
}

vector<ll> ans(n, 0);
for (int i = 0; i < n; ++i) {
int v = topological[i];
for (auto [u, t] : revGraph[v]) ans[v] = max(ans[v], ans[u] + t);
}

ll ansval = 0;
for (int i = 0; i < n; ++i) ansval += ans[i] * c[i];

cout << ansval << endl;
return 0;
}
3 changes: 3 additions & 0 deletions maximum-profits-by-toll/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"], challenge_cases=["*_task2.in", "*_all.in"])
expected_score(5)
33 changes: 33 additions & 0 deletions maximum-profits-by-toll/sol-cpp-partial1/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); ++i)

int main() {
int n, m;
cin >> n >> m;
vector<int> c(n);
rep(i, n) cin >> c[i];
vector Graph(n, vector<pair<int, int>>(0));
rep(_, m) {
int u, v, t;
cin >> u >> v >> t;
Graph[u - 1].push_back({ v - 1, t });
}

vector<int> indeg(n, 0);
rep(u, n) for (auto [v, _] : Graph[u]) indeg[v]++;
queue<int> q;
rep(u, n) if (indeg[u] == 0) q.push(u);
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto [v, _] : Graph[u])
if (--indeg[v] == 0) q.push(v);
}
bool isDag = true;
rep(u, n) if (indeg[u] > 0) isDag = false;

cout << (isDag ? 0 : -1) << endl;
return 0;
}
3 changes: 3 additions & 0 deletions maximum-profits-by-toll/sol-cpp-partial2/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"], challenge_cases=["*_all.in"])
expected_score(15)
57 changes: 57 additions & 0 deletions maximum-profits-by-toll/sol-cpp-partial2/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); ++i)

int main() {
int n, m;
cin >> n >> m;
vector<int> c(n);
rep(i, n) cin >> c[i];
vector edges(m, tuple<int, int, int>());
vector Graph(n, vector<pair<int, int>>(0));
vector revGraph(n, vector<pair<int, int>>(0));
rep(i, m) {
int u, v, t;
cin >> u >> v >> t;
Graph[u - 1].push_back({ v - 1, i });
revGraph[v - 1].push_back({ u - 1, i });
edges[i] = { u - 1, v - 1, t };
}

vector<int> topological(0);
vector<int> indeg(n, 0);
rep(u, n) for (auto [v, _] : Graph[u]) indeg[v]++;
queue<int> q;
rep(u, n) if (indeg[u] == 0) q.push(u);
while (!q.empty()) {
int u = q.front();
q.pop();
topological.push_back(u);
for (auto [v, _] : Graph[u])
if (--indeg[v] == 0) q.push(v);
}
bool isDag = true;
rep(u, n) if (indeg[u] > 0) isDag = false;

if (!isDag) {
cout << -1 << endl;
return 0;
}

vector<ll> ans(m, 0);
for (int i = n - 1; i >= 0; --i) {
int v = topological[i];
assert(revGraph[v].size() <= 1);
if (revGraph[v].empty()) continue;
int e = revGraph[v][0].second;
ans[e] = c[v];
for (auto [_, j] : Graph[v]) ans[e] += ans[j];
}

ll ansval = 0;
for (int i = 0; i < m; ++i) ansval += ans[i] * get<2>(edges[i]);

cout << ansval << endl;
return 0;
}
3 changes: 3 additions & 0 deletions maximum-profits-by-toll/sol-pypy-ac/SOLUTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8; mode: python -*-
script_solution(src='main.py')
expected_score(100)
Loading

0 comments on commit 6c0cb67

Please sign in to comment.