解法.強連結成分分解
番目の金属を頂点 として,弧を を加えた有向グラフ を考える. を強連結成分分解して,その強連結成分全体を とする.ただし,添字の昇順にトポロジカルソートされているとする.各強連結成分 内で生成を行うと, が1でない限り 内の総グラム数が減ることはない.また, 内に含まれる弧数の方が よりも真に大きい場合は 内の総グラム数は真に増加することが分かる.したがって, 内に最初のグラム数が正の頂点が存在するか, で から への道が存在して 内に最初のグラム数が正の頂点が存在する場合に, 内のすべての頂点は無限に生成することができる.
各頂点の出次数はちょうど 2 なので,弧数は頂点数のちょうど 2 倍となる.したがって,強連結成分分解は 時間で求まるので,頂点 が無限に生成可能かどうかは 時間で判定できる.
次に, が無限に生成できない場合に最大で何グラム得ることができるかを考える. を含む強連結成分を とする.このとき,強連結成分 内の頂点 を分解して のグラム数を増やすことを考える. の成分グラフを とする.ただし, は多重辺を含むとする.このとき, から への 上の異なる道の数を とすると, は によって グラムだけ増加する.
計算時間:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll MOD = 1e9 + 7;
struct Graph {
const int n;
std::vector<std::vector<int>> adj, radj;
std::vector<int> scc;
explicit Graph(int _n) : n(_n), adj(n), radj(n), scc(n, false) {}
void add_edge(const int src, const int dst) {
adj[src].push_back(dst); radj[dst].push_back(src);
}
void Dfs(const int cur, std::vector<int> &ord) {
scc[cur] = true;
for (auto dst : adj[cur])
if (!scc[dst]) Dfs(dst, ord);
ord.push_back(cur);
}
void RevDfs(const int id, const int cur) {
scc[cur] = id;
for (auto dst : radj[cur])
if (scc[dst] == -1) RevDfs(id, dst);
}
void StronglyConnectedComponents() {
std::vector<int> ord; ord.reserve(n);
for (int v = 0; v < n; ++v)
if (!scc[v]) Dfs(v, ord);
std::fill(scc.begin(), scc.end(), -1);
for (int i = n - 1, no = 0; 0 <= i; --i)
if (scc[ord[i]] == -1) RevDfs(no++, ord[i]);
}
};
class Solver {
public:
Solver(int _n) : num_case(_n) {}
void input() {
cin >> N;
g = new Graph(N);
for (int v = 0; v < N; ++v) {
for (int i = 0, u; i < 2; ++i) {
cin >> u;
g->add_edge(v, u - 1);
}
}
gram.resize(N);
for (auto &&x : gram) cin >> x;
}
void print() {
cout << "Case #" << num_case << ": ";
if (max_gram == -1) cout << "UNBOUNDED\n";
else cout << max_gram << "\n";
delete g;
}
void solve();
private:
const int num_case;
int N;
Graph *g;
vector<ll> gram;
ll max_gram = 0;
};
void Solver::solve() {
g->StronglyConnectedComponents();
const int num_comp = *max_element(g->scc.begin(), g->scc.end()) + 1;
vector<vector<int>> comp(num_comp);
for (int v = 0; v < N; ++v) comp[g->scc[v]].push_back(v);
vector<size_t> num_edges(num_comp);
for (int c = 0; c < num_comp; ++c) {
for (int v : comp[c]) {
for (int u : g->adj[v])
if (g->scc[u] == c) ++num_edges[c];
}
}
vector<bool> inf(num_comp, false);
vector<ll> num_path(num_comp, -1);
for (int c = num_comp - 1; 0 <= c; --c) {
if (c == g->scc[0]) {
num_path[c] = 1;
}
else {
for (int v : comp[c])
for (int u : g->adj[v]) {
if (num_path[g->scc[u]] != -1) {
if (num_path[c] == -1) num_path[c] = num_path[g->scc[u]];
else (num_path[c] += num_path[g->scc[u]]) %= MOD;
}
inf[c] = inf[c] or inf[g->scc[u]];
}
if (num_path[c] != -1 && 0 < num_edges[c])
inf[c] = true;
}
if (num_path[c] != -1 && comp[c].size() < num_edges[c])
inf[c] = true;
}
for (int v = 0; v < N; ++v) {
if (0 < gram[v]) {
const int k = g->scc[v];
if (inf[k]) { max_gram = -1; break; }
else if (num_path[k] != -1) (max_gram += (gram[v] * num_path[k]) % MOD) %= MOD;
}
}
}
int main() {
cin.tie(0); ios::sync_with_stdio(false);
int T; cin >> T;
for (int i_case = 1; i_case <= T; ++i_case) {
Solver s(i_case);
s.input();
s.solve();
s.print();
}
return 0;
}
成分グラフの多重辺を考えないでハマった.道の数の剰余をとらないでハマった.
成分グラフを陽に構成するよりも,トポロジカルソートの降順に行うと簡潔に実装できた.
大まかな解法はすぐ浮かんだので他の問題よりも取り掛かりやすかったかも.