题目描述
给你一张 \(n\) 点 \(m\) 边的有向图,第 \(i\) 个点点权为 \(F_{i}\),第 \(i\) 条边边权为 \(T_{i}\)。
找一个环,设环上的点组成的集合为 \(S\),环的边组成的集合为 \(E\),最大化\( \frac{\sum_{u \in S}F_{u}} {{\sum_{e \in E} T_{e}}}\)的值
思路分析
先给一个显而易见的小结论:任意一个环中的点数和边数一定相等 我们假设有\(m\)个
接着假设最大答案为\(ans\)
有
移项,
运用乘法分配律,
移项,使用加(减)法交换结合律,
得到结论:答案 \(ans\) 在\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\geq 0\)条件下合法 求解最大的\(ans\)
我们把\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\)设为\(f(ans)\) 得到\(f(ans)\)为增函数
尝试二分\(ans\) \((ans \in [10^{-3},10^3])\)
- case 1:\(f(ans)<0\) 不满足\(ans\)的条件 应增大\(ans\)的值 (代码
l=mid
) - case2:\(f(ans)\geq 0\)满足\(ans\)的条件 应减小\(ans\)的值(代码
r=mid
)
由此可以打出\(01\)分数规划二分的板子:
double l=0.001,r=1000;double mid;while(r-l>0.0001){mid=(l+r)/2.0;if(check(mid)<0) l=mid;else r=mid;}
想一想怎么打\(check()\)函数
考虑\(f(ans)<0\) 即\({\sum_{i}^m (T_{i}\times ans-F_{i}) }< 0\)
因为要求选的点形成一个环 而\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\)值又为负数 可以很显然想到负环算法
我们给每条边重新赋值边权 对于任意边\([u,v]\)边权赋值为\(w[u][v]\times ans-F_u\)
那么判断负环的公式就是\({\sum_{i}^m (T_{i}\times ans-F_{i}) }< 0\) 即\(f(ans)<0\)
这样就实现了用负环算法完成\(check\)函数
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+10;
int f[N];
struct edge
{int v,w;
};
vector<edge> e[N];
int num[N];
double dis[N];
bool vis[N];
int n,m;
bool spfa(double ans)
{queue<int> q;memset(num,0,sizeof(num));memset(vis,0,sizeof(vis));memset(dis,0,sizeof(dis));for(int i=1;i<=n;++i) {q.push(i);vis[i]=1;}while(!q.empty()) {int u=q.front();q.pop();vis[u]=0;for(int i=0;i<e[u].size();++i){int v=e[u][i].v;double w=e[u][i].w;w=w*ans-f[u];//重新赋值边权if(dis[v]>dis[u]+w){dis[v]=dis[u]+w;num[v]=num[u]+1;if(num[v]>=n) return 1;if(!vis[v]) {q.push(v);vis[v]=1;}}}}return 0;
}
signed main()
{ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>m;for(int i=1;i<=n;++i)cin>>f[i];for(int i=1;i<=m;++i){int u,v,w;cin>>u>>v>>w;e[u].push_back({v,w});}double l=0.001,r=1000;double mid;while(r-l>0.0001){mid=(l+r)/2.0;if(spfa(mid)) l=mid;else r=mid;}printf("%.2lf",mid);return 0;
}