345. 牛站
这题是说给 条边构成的图,求 到 恰经过 条边(可重复经过)的最短路,保证一定有解。
解法是借鉴矩阵快速幂思想的 floyd 算法,定义 表示从 到 恰经过 条边的最短路,有 f[a+b][i][j]=min(f[a+b][i][j], f[a][i][k]+f[b][k][j];
,把 进行二进制分解,然后倍增来求
C++
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 210;
int n, k, T, S, E;
map<int, int> mp;
int g[N][N], res[N][N];
void mul(int c[][N], int a[][N], int b[][N])
{
static int tmp[N][N];
memset(tmp, 0x3f, sizeof tmp);
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
tmp[i][j] = min(tmp[i][j], a[i][k] + b[k][j]);
memcpy(c, tmp, sizeof tmp);
}
void qmi()
{
memset(res, 0x3f, sizeof res);
for (int i = 1; i <= n; i++) res[i][i] = 0; // 经过 0 条边
while (k)
{
if (k & 1) mul(res, res, g);
mul(g, g, g);
k >>= 1;
}
}
int main()
{
cin >> k >> T >> S >> E; // 离散化
memset(g, 0x3f, sizeof g);
//这里我们来解释一下为什么不去初始化g[i][i]=0呢?
//我们都知道在类Floyd算法中有严格的边数限制,如果出现了i->j->i的情况其实在i->i中我们是有2条边的
//要是我们初始化g[i][i]=0,那样就没边了,影响了类Floyd算法的边数限制!
if (!mp.count(S)) mp[S] = ++ n;
if (!mp.count(E)) mp[E] = ++ n;
S = mp[S], E = mp[E];
while (T -- )
{
int a, b, c;
cin >> c >> a >> b;
if (!mp.count(a)) mp[a] = ++ n;
if (!mp.count(b)) mp[b] = ++ n;
a = mp[a], b = mp[b];
g[a][b] = g[b][a] = min(g[a][b], c);
}
qmi();
cout << res[S][E] << endl;
return 0;
}
1426. 魔法
这道题跟上面那道是一样的思想,都是借助快速幂思想做 floyd 。
题意说从 到 ,最多使用 次魔法,每次使用可以使当前经过的边权变为相反数,问最终的权和最小值。
核心思路是定义 表示从 到 最多使用 次魔法的最小值,枚举最后一个分界点 ,使得从 到 最多用 次魔法,从 到 最多用 1 次魔法。就有 f[k][i][j]=min{f[k][i][j], f[k-1][i][t]+f[1][t][j]};
核心是这里的取 min 运算同样满足结合律(把 min 换成加法就成了矩阵乘法,矩满足结合律才可以先算 (F(k-2)F(1)) F(1) = F(k-2) (F(1)F(1))
),那么就可以用矩阵快速幂去做了。实际上,取 min 和取 max 都满足结合律。
还有一些边界情况:k=0
时即 floyd 算法,k=1
时最多用 1 次魔法,可以枚举哪条边使用魔法。
C++
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 110, M = 2510;
int n, m, K;
LL d[N][N], f[N][N];
struct Edge
{
int a, b, c;
}edge[M];
void mul(LL c[][N], LL a[][N], LL b[][N])
{
static LL t[N][N];
memset(t, 0x3f, sizeof t);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
for (int k = 1; k <= n; k ++ )
t[i][j] = min(t[i][j], a[i][k] + b[k][j]);
memcpy(c, t, sizeof t);
}
LL qmi()
{
while (K)
{
if (K & 1) mul(d, d, f);
mul(f, f, f);
K >>= 1;
}
return d[1][n];
}
int main()
{
scanf("%d%d%d", &n, &m, &K);
memset(d, 0x3f, sizeof d);
for (int i = 1; i <= n; i ++ ) d[i][i] = 0;
for (int i = 0; i < m; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
d[a][b] = c;
edge[i] = {a, b, c};
}
// floyd
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
memcpy(f, d, sizeof f);
// 使用 1 次
for (int k = 0; k < m; k ++ )
{
int a = edge[k].a, b = edge[k].b, c = edge[k].c;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
f[i][j] = min(f[i][j], d[i][a] - c + d[b][j]);
}
printf("%lld\n", qmi());
return 0;
}