[HNOI2009]最小圈

题目描述

考虑带权的有向图G=(V,E)G=(V,E)以及w:ERw:E→R,每条边e=(i,j)(ij,iV,jV)e=(i,j)(i≠j,i∈V,j∈V)的权值定义为wi,jwi,j,令n=|V|n=|V|c=(c1,c2,,ck)(ciV)c=(c1,c2,⋯,ck)(ci∈V)GG中的一个圈当且仅当(ci,ci+1)(1i<k)(1≤i<k)(ck,c1)(ck,c1)都在EE中,这时称k为圈cc的长度同时令ck+1=c1,并定义圈c=(c1,c2,,ck)c=(c1,c2,⋯,ck)的平均值为μ(c)=i=1kwci,ci+1/kμ(c)=∑i=1kwci,ci+1/k,即cc上所有边的权值的平均值。令μ(c)=Min(μ(c))GG中所有圈c的平均值的最小值。现在的目标是:在给定了一个图G=(V,E)G=(V,E)以及w:ERw:E→R之后,请求出GG中所有圈c的平均值的最小值μ(c)=Min(μ(c))μ′(c)=Min(μ(c))
数据保证G=(V,E)G=(V,E)联通。

Solution

听别人说是01分数规划,但是我觉得这只是一个普通的二分。
二分答案,即最小环的平均值。
验证答案只需要把所有的边权都减去答案,之后判断一下有没有负环即可,如果有负环的话,就说明平均值过大(如果一个环上所有的边权都减去答案后,变成了一个负环,就说明这个环的平均值小于现在的二分出来的答案)。

对于下面这份代码,有几点要说明:
1.使用了不合理的常数优化 double l = -1e4, r = 1e4, res;实际上应该是1e7(不过可以估计出不专门卡的话,平均值不会特别大),或者是最大值和最小值(用正确性换时间)。
2.使用了不合理优化的BFS SPFA sum[v] = sum[now] + 1;应该是sum[v]++;,这种方法是我在网上见到的(似乎后来被证明有问题),但正确性还是比较高的(至少我用到现在还没出过错),而且速度很快(用正确性换时间)。
3.在luogu开O2过去了,但速度还是很慢。
4.听说此题不卡DFS版的SPFA,用DFS的话就可以忽视以上两点了(不知道出题人卡BFS,不卡DFS是什么心态)。
5.欢迎来卡。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>

const int maxn = 3007;
const int maxm = 10007;
const double esp = 1e-9;

class Map {
private :
    struct Node {
        int u, v, nex;
        double w;

        Node (int u = 0, int v = 0, double w = 0) :
            u(u),
            v(v),
            w(w),
            nex(0) {}
    };
    Node edge[maxm * 2];
    int head[maxn], cnt, vis[maxn], in[maxn], sum[maxn];
    double cost[maxn];
    int n;
    std :: queue<int> q;

public :
    void Init(int x) {
        n = x;
    }

    void AddEdge(int u, int v, double w) {
        edge[++cnt] = Node(u, v, w);
        edge[cnt].nex = head[u];
        head[u] = cnt;
    }

    bool SPFA(double dis) {
        while (!q.empty()) {
            q.pop();
        }
        memset(vis, 0, sizeof(vis));
        memset(sum, 0, sizeof(sum));
        memset(in, 0, sizeof(in));
        for (int i = 1; i <= n; i++) {
            cost[i] = 1e9 + 7;
        }
        cost[1] = 0;
        q.push(1);
        while (!q.empty()) {
            int now = q.front();
            q.pop();
            in[now] = 0;
            for (int i = head[now]; i; i = edge[i].nex) {
                int v = edge[i].v;
                double w = edge[i].w;
                w -= dis;
                if (cost[now] + w < cost[v]) {
                    cost[v] = cost[now] + w;
                    sum[v] = sum[now] + 1;
                    if (sum[v] == n) {
                        return 0;
                    }
                    if (!in[v]) {
                        in[v] = 1;
                        q.push(v);
                    }
                }
            }
        }
        return 1;
    }

};

class Solution {
private :
    int n ,m;
    Map mp;

    double Find() {
        double l = -1e4, r = 1e4, res;
        while (fabs(l - r) > esp && r >= l) {
            double mid = (l + r) / 2; //mid表示答案 
            if (mp.SPFA(mid)) { //
                res = mid;
                l = mid + esp;
            } else {
                r = mid - esp;
            }
        }
        return res;
    }

public :
    Solution () {
        sol.Get();
        sol.Solve();
    }
    void Get() {
        scanf("%d %d", &n, &m);
        mp.Init(n);
        for (int i = 1; i <= m; i++) {
            int u, v;
            double w;
            scanf("%d %d %lf", &u, &v, &w);
            mp.AddEdge(u, v, w);
        }
    }

    void Solve() {
        printf("%.8lf\n", Find());
    }
};
Solution sol;

int main() {}

这辈子都不可能写主函数的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值