题解:洛谷 P3199 [HNOI2009] 最小圈
【题目来源】
洛谷:[P3199 HNOI2009] 最小圈 - 洛谷
【题目描述】
考虑带权有向图 \(G=(V,E)\) 以及 \(w:E\rightarrow \R\),每条边$ e=(i,j)\((\)i\neq j\(,\)i, j\in V$)的权值定义为 \(w_{i,j}\)。设 \(n=|V|\)。
\(c=(c_1,c_2,\cdots,c_k)\)(\(c_i\in V\))是 \(G\) 中的一个圈当且仅当 \((c_i,c_{i+1})\)(\(1\le i<k\))和 \((c_k,c_1)\) 都在 \(E\) 中。称 \(k\) 为圈 \(c\) 的长度,同时记 \(c_{k+1}=c_1\),并定义圈 \(c=(c_1,c_2,\cdots,c_k)\) 的平均值为
\(\mu(c)= \frac 1 k \sum\limits_{i=1}^{k} w_{c_i,c_{i+1}}\)
即 \(c\) 上所有边的权值的平均值。设 \(\mu'(G)=\min_c\mu(c)\) 为 \(G\) 中所有圈 \(c\) 的平均值的最小值。
给定图 \(G=(V,E)\) 以及 \(w:E\rightarrow \R\),求出 \(G\) 中所有圈 \(c\) 的平均值的最小值 \(\mu'(G)\)。
【输入】
第一行两个正整数,分别为 \(n\) 和 \(m\),并用一个空格隔开。其中 \(n=|V|\),\(m=|E|\) 分别表示图中有 \(n\) 个点 和 \(m\) 条边。
接下来 \(m\) 行,每行三个数 \(i,j,w_{i,j}\),表示有一条边 \((i,j)\) 且该边的权值为 \(w_{i,j}\),注意边权可以是实数。输入数据保证图 \(G=(V,E)\) 连通,存在圈且有一个点能到达其他所有点。
【输出】
一个实数 \(\mu'(G)\),要求精确到小数点后 \(8\) 位。
【输入样例】
4 5
1 2 5
2 3 5
3 1 5
2 4 3
4 1 3
【输出样例】
3.66666667
【算法标签】
《洛谷 P3199 最小圈》 #各省省选# #湖南# #2009#
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 3005; // 最大节点数
const int M = 10005; // 最大边数
int n, m; // n: 节点数, m: 边数
int vis[N]; // 访问标记数组
int h[N], ne[M], to[M], tot; // 链式前向星存储图
double d[N], w[M]; // d: 距离数组, w: 边权数组
// 链式前向星添加边
void add(int a, int b, int c)
{
to[++tot] = b; // 存储终点
ne[tot] = h[a]; // 存储下一条边
w[tot] = c; // 存储边权
h[a] = tot; // 更新头指针
}
// SPFA算法检测负环(带偏移量x)
bool spfa(int u, double x)
{
vis[u] = 1; // 标记当前节点已访问
for (int i = h[u]; i; i = ne[i]) // 遍历所有邻边
{
int v = to[i];
if (d[v] > d[u] + w[i] - x) // 松弛操作(带偏移量)
{
d[v] = d[u] + w[i] - x;
if (vis[v] || spfa(v, x)) // 如果已访问或发现负环
return true;
}
}
vis[u] = 0; // 回溯时取消标记
return false;
}
// 检查是否存在负环(带偏移量x)
bool check(double x)
{
memset(d, 0x3f, sizeof(d)); // 初始化距离为无穷大
memset(vis, 0, sizeof(vis)); // 清空访问标记
for (int i = 1; i <= n; i++) // 检查每个连通分量
if (spfa(i, x))
return true;
return false;
}
// 二分查找最小平均权值
double find()
{
double l = -1e7, r = 1e7; // 初始化二分范围
while (r - l > 1e-10) // 精度控制
{
double mid = (l + r) / 2;
if (check(mid)) // 如果存在负环
r = mid; // 尝试更小的平均值
else
l = mid; // 尝试更大的平均值
}
return r; // 返回最小平均权值
}
int main()
{
cin >> n >> m;
// 输入边信息并建图
for (int i = 1; i <= m; i++)
{
int x, y, z;
cin >> x >> y >> z;
add(x, y, z);
}
// 输出最小平均权值(保留8位小数)
printf("%.8f\n", find());
return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;
const int N = 3005; // 最大节点数
const int M = 10005; // 最大边数
int n, m; // n: 节点数, m: 边数
int vis[N]; // 访问标记数组
int h[N], e[M], ne[M], idx=1; // 链式前向星存储图(idx从1开始)
int to[M]; // 边的终点数组(未使用)
double d[N], w[M]; // d: 距离数组, w: 边权数组
// 链式前向星添加边
void add(int a, int b, int c)
{
e[idx] = b; // 存储终点
w[idx] = c; // 存储边权
ne[idx] = h[a]; // 存储下一条边
h[a] = idx++; // 更新头指针
}
// SPFA算法检测负环(带偏移量x)
bool spfa(int u, double x)
{
vis[u] = 1; // 标记当前节点已访问
for (int i = h[u]; i; i = ne[i]) // 遍历所有邻边
{
int j = e[i];
if (d[j] > d[u] + w[i] - x) // 松弛操作(带偏移量)
{
d[j] = d[u] + w[i] - x;
if (vis[j] || spfa(j, x)) // 如果已访问或发现负环
return true;
}
}
vis[u] = 0; // 回溯时取消标记
return false;
}
// 检查是否存在负环(带偏移量x)
bool check(double x)
{
memset(d, 0x3f, sizeof(d)); // 初始化距离为无穷大
memset(vis, 0, sizeof(vis)); // 清空访问标记
for (int i = 1; i <= n; i++) // 检查每个连通分量
if (spfa(i, x))
return true;
return false;
}
// 二分查找最小平均权值
double find()
{
double l = -1e7, r = 1e7; // 初始化二分范围
double eps = 1e-10; // 精度控制
while (r - l > eps) // 精度控制循环
{
double mid = (l + r) / 2;
if (check(mid)) // 如果存在负环
r = mid; // 尝试更小的平均值
else
l = mid; // 尝试更大的平均值
}
return l; // 返回最小平均权值
}
int main()
{
cin >> n >> m;
// 输入边信息并建图
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
// 输出最小平均权值(保留8位小数)
printf("%.8f\n", find());
return 0;
}
【运行结果】
4 5
1 2 5
2 3 5
3 1 5
2 4 3
4 1 3
3.66666667
浙公网安备 33010602011771号