Loading

[USACO07DEC] Sightseeing Cows G

算法

初看题面没有思路, 考虑使用数学语言表示

注意本题最重要的信息是发现路径为一个环

给你一张 \(n\)\(m\) 边的有向图,第 \(i\) 个点点权为 \(F_i\) , 第 \(i\) 条边边权为 \(T_i\)

找一个环, 设环上的点组成的集合为 \(S\) , 环的边组成的集合为 \(E\) , 令

\[\frac{\sum_{u\in S}F_u}{\sum_{e\in E}T_e} \to max \]


这个形式与 \(0\)/\(1\) 分数规划很相似

考虑 \(0\)/\(1\) 分数规划的方式

\[\frac{ \sum_{u \in S} F_us_u} {\sum_{e\in E}T_es_u} \geq x \]

移项得,

\[f = \sum_{u \in S, e \in E} (F_u - xT_e)s_u \geq 0 \]

现在我们可以将图中的边权化为 \(F_u - xT_e\) , 然后只需要找到一个环, 看路径总长度是否大于 \(0\) , 二分是显然的

观察到这是一个典型的 \(\rm{spfa}\) 找正环的问题, 于是就可以打代码了

代码

实现上, 这里有一个类似于 \(\rm{Tarjan}\) 算法的问题, 边连接着两个点, 我们不好去判断 \(F_u, T_e\) 怎么改变边的权值

对于环, 我们只需要记录当前点出去的边为 \(T_e\) 即可, 反正最后会绕回去

所以也就简单了

#include <bits/stdc++.h>
#define int long long
const int MAXM = 5e3 + 1145;
const int MAXN = 1e3 + 30;
const double eps = 1e-5;

int n, m;
int Val[MAXN];

class Graph_Class
{
private:

public:
    struct node
    {
        int to; double w;
        int next;
    } Edge[MAXM];
    int Edge_Cnt = 0;
    int head[MAXN];
    void head_init() { for (int i = 0; i <= n; i++) { head[i] = -1; } }

    void init(){ head_init(); }

    void addedge(int u, int v, double w) {
        Edge[++Edge_Cnt].to = v;
        Edge[Edge_Cnt].w = w;
        Edge[Edge_Cnt].next = head[u];
        head[u] = Edge_Cnt;
    }
} Graph;

class Sol_Class
{
private:

    double dis[MAXN];
    int vis[MAXN];
    bool inq[MAXN];
    std::queue<int> Q;

    void init() {
        memset(dis, -0x3f, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        while (!Q.empty()) Q.pop();
        memset(inq, false, sizeof(inq));
    }

    bool spfa(int Start, double x)
    {
        init();
        Q.push(Start), dis[Start] = 0, vis[Start]++, inq[Start] = true;

        while (!Q.empty())
        {
            int Now = Q.front();
            Q.pop();
            inq[Now] = false;

            for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next) {
                int NowTo = Graph.Edge[i].to;
                double NowW = Val[Now] * 1.0 - x * Graph.Edge[i].w;
                if (dis[NowTo] < dis[Now] + NowW) {
                    dis[NowTo] = dis[Now] + NowW;

                    if (!inq[NowTo]) {
                        inq[NowTo] = true;
                        Q.push(NowTo);
                        vis[NowTo]++;
                        if (vis[NowTo] > n) return true;
                    }
                }
            }
        }
        return false;
    }

    /*只需要判断是否有正环即可, 有就可以返回 true*/
    bool check(double x) {
        return spfa(0, x);
    }

public:
    void solve()
    {
        double Left = 0, Right = 1024;
        double ans = -1;
        while (Right - Left > eps) {
            double Mid = Left + (Right - Left) / 2;
            if (check(Mid)) ans = Mid, Left = Mid;
            else Right = Mid;
        }

        printf("%.2lf", ans);
    }
} Sol;

signed main()
{
    scanf("%lld %lld", &n, &m);
    Graph.init();
    for (int i = 1; i <= n; i++)
        scanf("%lld", &Val[i]);
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        scanf("%lld %lld %lld", &u, &v, &w);
        Graph.addedge(u, v, w);
    }

    /*建立超级源点*/
    for (int i = 1; i <= n; i++)
        Graph.addedge(0, i, 0);
    
    Sol.solve();

    return 0;
}

总结

初看题面没有思路, 考虑使用数学语言表示

注意超级源点会使 \(MAXM\) 变大

posted @ 2024-11-16 17:11  Yorg  阅读(18)  评论(0)    收藏  举报