题解【洛谷P2868】[USACO07DEC]Sightseeing Cows G

题面

题意简述:

给定一张 \(L\) 个点、\(P\) 条边的 有向图 ,每个点都有一个权值 \(f_i\),每条边都有一个权值\(t_i\)

求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。

输出这个最大值。

注意:数据保证至少存在一个环。

\(\text{Data Range}:2 \leq L \leq 1000,2\leq P \leq 5000,1\leq f_i,t_i\leq1000\)

一道 \(0/1\) 分数规划的题目。

题目要我们求的就是一个有 \(x\) 个点的环上 \(\frac{\sum_{i=1}^x f_i}{\sum_{i=1}^x t_i}\) 的最大值。

二分答案。

设二分的值为实数 \(mid\),即 \(\frac{\sum_{i=1}^x f_i}{\sum_{i=1}^x t_i} > mid\)

我们开始化简式子:

\[\sum_{i=1}^x f_i > mid \times \sum_{i=1}^x{t_i} \]

\[\sum_{i=1}^x f_i - mid \times \sum_{i=1}^x{t_i} > 0 \]

\[\sum_{i=1}^x {(f_i-mid\times t_i)} > 0 \]

问题就变成了求一个边权为 \(f_i-mid\times t_i\) 的图中是否存在正环。

类似于求负环,我们在每一次 SPFA 求 最长路 的过程中改变一下 更新最长路长度 的形式,变成 \(dist_v < dist_u + mid \times w_i\),并统计一下当前点的最长路中点的个数,如果点数 大于等于 \(n\) 就说明存在正环。

注意浮点数二分的边界条件,哪些变量和数组需要开 double 类型。

#include <bits/stdc++.h>

using namespace std;

const int N = 1003, M = 5003;

int n, m;
int tot, head[N], ver[M], nxt[M], edge[M];
int f[N]; //点权
double dist[N]; //每个点开始最长路的长度
int cnt[N]; //每个点最长路上点的个数
bool st[N];

inline void add(int u, int v, int w)
{
    ver[++tot] = v, edge[tot] = w, nxt[tot] = head[u], head[u] = tot;
}

inline bool SPFA(double x)
{
    queue <int> q;
    memset(cnt, 0, sizeof cnt);
    memset(dist, 0, sizeof dist);
    for (int i = 1; i <= n; i+=1)
    {
        q.push(i); //一开始先将每个点加进队列
        st[i] = true;
    }
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        st[u] = false;
        for (int i = head[u]; i; i = nxt[i])
        {
            int v = ver[i], w = edge[i];
            if (dist[v] < dist[u] + f[u] - x * w) 
            {
                dist[v] = dist[u] + f[u] - x * w; //更新最长路的长度
                cnt[v] = cnt[u] + 1; //更新最长路上点的个数
                if (cnt[v] >= n) return true; //存在一个正环
                if (!st[v])
                {
                    st[v] = true;
                    q.push(v);
                }
            }
        }
    }
    return false; //不存在正环
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i+=1)
        cin >> f[i];
    for (int i = 1; i <= m; i+=1)
    {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w); //注意加的是有向边
    }
    double l = 0, r = 1003.0;
    while (r - l > 1e-4)
    {
        double mid = (l + r) / 2.0;
        if (SPFA(mid)) l = mid;
        else r = mid;
    }
    printf("%.2lf\n", l);
    return 0;
}
posted @ 2020-03-03 12:53  csxsi  阅读(176)  评论(0编辑  收藏  举报