题目描述
求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。
输出这个最大值。
注意:数据保证至少存在一个环。
输入格式
第一行包含两个整数L和P。
接下来L行每行一个整数,表示f[i]。
再接下来P行,每行三个整数a,b,t[i],表示点a和b之间存在一条边,边的权值为t[i]。
输出格式
输出一个数表示结果,保留两位小数。
数据范围
2≤L≤1000,
2≤P≤5000,
1≤f[i],t[i]≤1000
样例
输入样例:
5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2
输出样例:
6.00
用spfa求负环的两个问题:
1.为什么一开始将所有点入队:
等价于加入一个虚拟源点,求从虚拟源点到每个点的最短路径,并且从虚拟源点到每个点的边权等于零,因此虚拟源点入队更新后会将所有点加入队列,等价于一开始就将所有点加入队列。
2.为什么dist不用初始化为正无穷或或零:
由于图中存在负环,并且是判断有没有负环,因此不需要用dist储存距离(求最短路径时要初始化为正无穷,因为要求最短路径,即最短距离),此处的dist只需要来判断是否比上一次距离小,是否需要更新,而当存在负环时,处在负环上的点是需要无限更新的,每绕一圈,路径就会减小,因此cnt是不断增大的,只需要根据cnt来判断是否存在负环即可
C++ 代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1010,M=5010;
int n,m;
int wf[N];
int h[N],e[M],wt[M],ne[M],idx;
double dist[N];
int cnt[N];
bool st[N];
void add(int a,int b,int c){
e[idx]=b,wt[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool check(double mid){
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
memset(dist,0,sizeof dist);
queue<int>q;
int hh=0,tt=0;
for(int i=1;i<=n;i++){
st[i]=true;
q.push(i);
}
while(q.size()){
int x=q.front();
q.pop();
st[x]=false;
for(int i=h[x];~i;i=ne[i]){
int j=e[i];
if(dist[j]<dist[x]+wf[x]-mid*wt[i]){
dist[j]=dist[x]+wf[x]-mid*wt[i];
cnt[j]=cnt[x]+1;
if(cnt[j]>=n)return true;
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)cin>>wf[i];
while(m--){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
double l=0,r=1e6;
while(r-l>1e-4){
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%.2lf\n",l);
return 0;
}