观光奶牛
给定一张L个点、P条边的有向图,每个点都有一个权值f[i],每条边都有一个权值t[i]。
求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。
输出这个最大值。
注意:数据保证至少存在一个环。
输入格式
第一行包含两个整数L和P。
接下来L行每行一个整数,表示f[i]。
再接下来P行,每行三个整数a,b,t[i],表示点a和b之间存在一条边,边的权值为t[i]。
输出格式
输出一个数表示结果,保留两位小数。
数据范围
2
≤
L
≤
1000
2≤L≤1000
2≤L≤1000,
2
≤
P
≤
5000
2≤P≤5000
2≤P≤5000,
1
≤
f
[
i
]
,
t
[
i
]
≤
1000
1≤f[i],t[i]≤1000
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
题解:
首先说下0,1规划一般的问法就是一个分数形式的表达式的最大值。01分数规划一般都是使用二分来求解的。这道题我们来分析一下他求的是一个环上点权之和/环上边权之和的最大值。又有点又有边,我们一般是把点权分到边上这样方便一些。
∑
i
n
(
f
(
i
)
)
/
∑
i
n
(
f
(
i
)
)
>
m
i
d
\sum_{i}^{n}(f(i))/\sum_{i}^{n}(f(i))>mid
∑in(f(i))/∑in(f(i))>mid
∑
i
n
(
f
(
i
)
)
>
m
i
d
∗
∑
i
n
(
f
(
i
)
)
\sum_{i}^{n}(f(i))>mid*\sum_{i}^{n}(f(i))
∑in(f(i))>mid∗∑in(f(i))
当我们把点权分到了边权上面继续化简得到
∑
i
n
(
f
(
i
)
−
m
i
d
∗
(
f
(
i
)
)
)
>
0
\sum_{i}^{n}(f(i)-mid*(f(i)))>0
∑in(f(i)−mid∗(f(i)))>0
这一步目的就很清晰了,也就是求一个正环。mid是我们二分的一个答案,所以我们在spfa的是改成一个最长路就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+7;
int ne[N],e[N],we[N],wf[N],head[N],cnt;
int vis[N],n,m,con[N];
double dis[N];
void add(int a,int b,int c)
{
e[cnt]=b,we[cnt]=c,ne[cnt]=head[a],head[a]=cnt++;
}
bool spfa(double mid)
{
memset(dis,0,sizeof dis);
memset(vis,0,sizeof vis);
memset(con,0,sizeof con);
queue<int> q;
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=head[u];i!=-1;i=ne[i]){
int j=e[i];
if(dis[j]<dis[u]+wf[u]-mid*we[i]){
dis[j]=dis[u]+wf[u]-mid*we[i];
con[j]=con[u]+1;
if(con[j]>=n) return 1;
if(!vis[j]){
q.push(j);
vis[j]=1;
}
}
}
}
return 0;
}
int main()
{
cin>>n>>m;
memset(head,-1,sizeof head);
for(int i=1;i<=n;i++) cin>>wf[i];
for(int i=1;i<=m;i++){
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(spfa(mid)) l=mid;
else r=mid;
}
printf("%.2lf\n",l);
return 0;
}