題幹:
給定一張L個點、P條邊的有向圖,每個點都有一個權值f[i],每條邊都有一個權值t[i]。
求圖中的一個環,使“環上各點的權值之和”除以“環上各邊的權值之和”最大。
輸出這個最大值。
注意:數據保證至少存在一個環。
輸入格式
第一行包含兩個整數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
思路:
根據題意,我們要求的首先是一個正環,然後需要讓這個環上的點權和除以邊權和最大。,fi表示點權,ei表示邊權。
既然是有向圖而已要有環,我們可以把邊權放在出點上,這樣一個點即表示了邊權也表示了點權,會簡單一些。。。
這就是一個比較標準的01分數規劃模型即其中xi=0或xi=1。
我們設一組{}滿足
①若存在,則:
即mid比要求的最大值小
②若不存在,則:
即mid比要求的最大值大
這就形成了一個二分判斷,即01分數規劃問題可以用二分來做。
然後問題就轉換成了找到一個正環,二分查找。
然後思考二分的左右邊界,因爲是有向圖,所以左邊界是,右邊界是
然後注意下因爲要輸出分數,所以mid和dis[]要用double。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010,M=5010;
int n,m;
int f[N],q[N],cnt[N];
double dis[N];
int idx,h[N],ne[M],e[M],wt[M];
bool vis[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(dis,0,sizeof(dis));
memset(cnt,0,sizeof(cnt));
memset(vis,0,sizeof(vis));
int hh=0,tt=0;
for(int i=1;i<=n;i++){
q[tt++]=i;
vis[i]=true;
}
while(tt!=hh){
int t=q[hh++];
if(hh==N) hh=0;
vis[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dis[j]<dis[t]+f[t]-mid*wt[i]){
dis[j]=dis[t]+f[t]-mid*wt[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!vis[j]){
q[tt++]=j;
if(tt==N) tt=0;
vis[j]=true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&f[i]);
memset(h,-1,sizeof(h));
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
double l=0,r=1e6;
while(r-l>1e-5){
double mid=(r+l)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2lf\n",l);
return 0;
}