洛谷:最小密度路径
给定一个图,Q次询问两个节点之间的最小密度 (密度的定义为,两点之间路径上边的权值和 除以 边的数量)
观察数据,类多源最短路,显然用 Floyd 求解
-
但普通 Floyd 维护的是两点间的最短路,此题要维护的是自定义的密度
-
密度尽可能小,相当于需要路径上边权和尽可能小、边数量尽可能多,同时维护两个变量是比较困难的
所以可以考虑先固定一个变量,再维护另一个变量的所有情况;枚举所有固定的情况,即所有最优子结构,就能求出全局最优解
所以我们选择枚举边数
-
定义 dp 集合: f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示从 i i i 到 j j j 经过 k k k 条边的最短路
-
考虑 dp 状态转移:显然我们有 f [ i ] [ j ] [ k ] = f [ i ] [ t ] [ a ] + f [ t ] [ j ] [ b ] f[i][j][k] = f[i][t][a]+f[t][j][b] f[i][j][k]=f[i][t][a]+f[t][j][b] (a + b == k)
-
但这样 a、b都需要去枚举,变成五层循环了时间上没法接受;所以优化一下转移,每次用前一层维护出后一层
-
于是得到了 f [ i ] [ j ] [ k ] = f [ i ] [ t ] [ k − 1 ] + f [ t ] [ j ] [ 1 ] f[i][j][k] = f[i][t][k-1]+f[t][j][1] f[i][j][k]=f[i][t][k−1]+f[t][j][1] (0 < k < n)
Floyd 预处理后,询问时枚举边数(有向无环图,最短路显然不会超过n - 1条边), f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k] 中取最小的 ( f [ i ] [ j ] [ k ] / k ) (f[i][j][k]/k) (f[i][j][k]/k) (0<k<n) 即是答案
代码:
#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
#define fx first
#define fy second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1)
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
typedef pair <int, PII> PI;
const int N = 60, M = 1010, MM = N;
int INF = 0x3f3f3f3f, mod = 100003;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int f[N][N][M];
void floyd() {
for (int g = 2; g <= n; g++)//枚举边数(观察发现不需要枚举到 m)
for (int k = 1; k <= n; k++)//
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j][g] = min(f[i][j][g], f[i][k][g - 1] + f[k][j][1]);
//变式floyd,在枚举第三个转换点外再加了一层枚举边的状态转移
}
int main() {
cin >> n >> m;
for (int k = 1; k <= m; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j][k] = INF;//初始化极值
for (int i = 1; i <= m; i++)
{
int a, b, x;
sca("%d%d%d", &a, &b, &x);
f[a][b][1] = min(f[a][b][1], x);//a -> b的一条有向边,注意重边
}
floyd();
cin >> k;
while (k--)
{
int a, b;
sca("%d%d", &a, &b);
double ans = 1e18;
for (int i = 1; i <= n; i++)//每经过 i 条边的状态都在之前维护成最优解
//枚举所有局部、子结构,就能得到全局最优解,是一种普遍求最优解的方法
if (f[a][b][i] != INF)
ans = min(ans, (double)f[a][b][i] / i);
if (ans != 1e18)pri("%.3f\n", ans);
else puts("OMG!");
}
return 0;
}
dpyyds