算法
初看题面没有思路, 考虑使用数学语言表示
注意本题最重要的信息是发现路径为一个环
给你一张 \(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\) 变大