题干:
给定一张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
思路:
根据题意,我们要求的首先是一个正环,然后需要让这个环上的点权和除以边权和最大。
∑
f
i
/
∑
e
i
\sum_{}f_i / \sum_{}e_i
∑fi/∑ei,fi表示点权,ei表示边权。
既然是有向图而已要有环,我们可以把边权放在出点上,这样一个点即表示了边权也表示了点权,会简单一些。。。
这就是一个比较标准的01分数规划模型即
∑
i
=
1
n
f
i
∗
x
i
∑
i
=
1
n
e
i
∗
x
i
\frac{\sum_{i=1}^{n}f_i*x_i}{\sum_{i=1}^{n}e_i*x_i}
∑i=1nei∗xi∑i=1nfi∗xi其中xi=0或xi=1。
我们设一组{
x
1
,
x
2
.
.
.
x
n
x_1,x_2...x_n
x1,x2...xn}满足
∑
i
=
1
n
(
f
i
−
m
i
d
∗
e
i
)
>
=
0
\sum_{i=1}^{n}(f_i-mid*e_i)>=0
∑i=1n(fi−mid∗ei)>=0
①若存在,则:
∑
i
=
1
n
f
i
∗
x
i
∑
i
=
1
n
e
i
∗
x
i
>
=
m
i
d
\frac{\sum_{i=1}^{n}f_i*x_i}{\sum_{i=1}^{n}e_i*x_i}>=mid
∑i=1nei∗xi∑i=1nfi∗xi>=mid
即mid比要求的最大值小
②若不存在,则:
∑
i
=
1
n
f
i
∗
x
i
∑
i
=
1
n
e
i
∗
x
i
<
m
i
d
\frac{\sum_{i=1}^{n}f_i*x_i}{\sum_{i=1}^{n}e_i*x_i}<mid
∑i=1nei∗xi∑i=1nfi∗xi<mid
即mid比要求的最大值大
这就形成了一个二分判断,即01分数规划问题可以用二分来做。
然后问题就转换成了找到一个正环,二分查找
∑
f
i
/
∑
e
i
?
m
i
d
\sum_{}f_i / \sum_{}e_i ?mid
∑fi/∑ei?mid。
然后思考二分的左右边界,因为是有向图,所以左边界是
∑
i
=
1
1000
1
/
∑
i
=
1
5000
1000
≈
0
\sum_{i=1}^{1000}1 / \sum_{i=1}^{5000}1000 \approx 0
∑i=110001/∑i=150001000≈0,右边界是
∑
i
=
1
1000
1000
/
∑
i
=
1
1000
1
=
1000
\sum_{i=1}^{1000}1000 / \sum_{i=1}^{1000}1 =1000
∑i=110001000/∑i=110001=1000
然后注意下因为要输出分数,所以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;
}