题解:洛谷 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
posted @ 2026-03-14 22:33  团爸讲算法  阅读(1)  评论(0)    收藏  举报