算法
初看题面没有思路, 考虑使用数学语言表示
注意本题最重要的信息是发现路径为一个环
给你一张 \(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\) 变大
标签:Now,int,double,USACO07DEC,Graph,Cows,dis,NowTo,Sightseeing From: https://www.cnblogs.com/YzaCsp/p/18549518