Atcoder Beginner Contest 236 G Good Vertices
G. Good Vertices
题目大意
给定一张有向图,初始无边,然后每一时刻都会加一条边,可能会有自环,但无重边。问每个点最早在第几时刻,存在一条从\(1\)号点到该点的长度为\(l\)的路径。
解题思路
问题首先可以转化成,求\(1\)号点到\(i\)号点的所有 长度为\(l\)路径中添加边时刻的最大值 的最小值。
设\(dp[i][j]\)表示从 \(1\)号点出发长度为 \(l\)到达 \(i\)号点的答案,则 \(dp[i][j] = \min_k(\max(dp[k][j - 1], w(k, i)))\),其中 \(w(k,i)\)表示边 \((k,i)\)添加的时刻。
时间复杂度为 \(O(n^2 L)\),而 \(L\)高达 \(10^9\)。
关于路径长度的问题一般的优化就是矩阵快速幂来求,但一般都是需要递推式是线性的。
从官方题解的描述来看,这里涉及到两个运算 \(\min\)和 \(\max\),在抽象代数中 \((Z \cup {\infty}, min, max)\)是构成半环(环的基础上把逆元条件去掉)的,即 \((Z \cup {\infty}, min)\)是交换幺半群, \((Z, max)\)是半群,(当然反过来也是),由此俩运算组成的矩阵乘法具有结合律,因而可以用矩阵快速幂优化。
虽说这学期刚学了近世代数,对于半环的矩阵运算具有结合律留待证明
神奇的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 1e9 + 7;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, t, l;
cin >> n >> t >> l;
vector<vector<int>> ma(n, vector<int>(n, inf));
auto result = ma;
for(int i = 1; i <= t; ++ i){
int u, v;
cin >> u >> v;
-- u;
-- v;
ma[u][v] = i;
}
auto multi = [](const vector<vector<int>> &a, const vector<vector<int>> &b){
vector<vector<int>> c(a.size(), vector<int>(b[0].size(), inf));
for(int i = 0; i < a.size(); ++ i)
for(int j = 0; j < b[0].size(); ++ j)
for(int k = 0; k < a[0].size(); ++ k){
c[i][j] = min(c[i][j], max(a[i][k], b[k][j]));
}
return c;
};
for(int i = 0; i < n; ++ i)
result[i][i] = -inf;
while(l){
if (l & 1)
result = multi(result, ma);
ma = multi(ma, ma);
l >>= 1;
}
for(int i = 0; i < n; ++ i){
if (result[0][i] == inf)
result[0][i] = -1;
cout << result[0][i] << " \n"[i == n - 1];
}
return 0;
}