SPFA(判负环) + 二分(01分数规划) - Sightseeing Cows - POJ 3621
题意:
给定一张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 2≤L≤1000, 2≤P≤5000, 1≤f[i],t[i]≤1000 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
分析:
本 题 目 标 : 对 图 中 每 个 环 , 求 m a x ( ∑ f [ i ] ∑ t [ i ] ) , 1 ≤ i ≤ n 。 本题目标:对图中每个环,求max(\frac{\sum f[i]}{\sum t[i]}),1≤i≤n。 本题目标:对图中每个环,求max(∑t[i]∑f[i]),1≤i≤n。
01分数规划 通 常 用 于 求 解 最 高 ′ ′ 性 价 比 ′ ′ 的 问 题 。 通常用于求解最高''性价比''的问题。 通常用于求解最高′′性价比′′的问题。
我 们 可 以 二 分 最 大 值 m i d , 接 着 判 断 是 否 存 在 某 个 环 满 足 m a x ( ∑ f [ i ] ∑ t [ i ] ) > m i d , 我们可以二分最大值mid,接着判断是否存在某个环满足max(\frac{\sum f[i]}{\sum t[i]})>mid, 我们可以二分最大值mid,接着判断是否存在某个环满足max(∑t[i]∑f[i])>mid,
若 存 在 , 说 明 答 案 在 区 间 [ m i d , r ] 之 间 , 否 则 答 案 在 [ l , m i d ] 之 间 。 若存在,说明答案在区间[mid,r]之间,否则答案在[l,mid]之间。 若存在,说明答案在区间[mid,r]之间,否则答案在[l,mid]之间。
答 案 的 上 界 为 1000 1 = 1 , 下 界 为 1 1000 。 所 以 二 分 的 区 间 取 [ 0 , 1000 ] 。 答案的上界为\frac{1000}{1}=1,下界为\frac{1}{1000}。所以二分的区间取[0,1000]。 答案的上界为11000=1,下界为10001。所以二分的区间取[0,1000]。
转化:
对 不 等 式 : ∑ f [ i ] ∑ t [ i ] > m i d , 对不等式:\frac{\sum f[i]}{\sum t[i]}>mid, 对不等式:∑t[i]∑f[i]>mid,
等 价 于 ∑ f [ i ] > m i d × ∑ t [ i ] , 等价于\sum f[i]>mid×\sum t[i], 等价于∑f[i]>mid×∑t[i],
等 价 于 ∑ f [ i ] − m i d × ∑ t [ i ] > 0 , 等价于\sum f[i]-mid×\sum t[i]>0, 等价于∑f[i]−mid×∑t[i]>0,
等 价 于 ∑ ( f [ i ] − m i d × t [ i ] ) > 0 。 等价于\sum (f[i]-mid×t[i])>0。 等价于∑(f[i]−mid×t[i])>0。
我 们 可 以 将 每 个 点 的 权 值 累 加 到 对 应 的 出 边 的 权 值 上 , 我们可以将每个点的权值累加到对应的出边的权值上, 我们可以将每个点的权值累加到对应的出边的权值上,
对 于 二 分 的 每 一 个 m i d , 将 图 中 所 有 的 边 权 转 化 为 f [ i ] − m i d × t [ i ] , 对于二分的每一个mid,将图中所有的边权转化为f[i]-mid×t[i], 对于二分的每一个mid,将图中所有的边权转化为f[i]−mid×t[i],
问 题 转 化 为 新 的 图 中 是 否 存 在 正 环 。 问题转化为新的图中是否存在正环。 问题转化为新的图中是否存在正环。
此 时 跑 一 遍 s p f a 即 可 。 此时跑一遍spfa即可。 此时跑一遍spfa即可。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1010, M=5010;
int n,m,wf[N];
int e[M],ne[M],wt[M],h[N],idx;
double dis[N];
int cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],wt[idx]=c,h[a]=idx++;
}
bool check(double mid)
{
memset(st,false,sizeof st);
memset(cnt,0,sizeof cnt);
queue<int> Q;
for(int i=1;i<=n;i++)
{
Q.push(i);
st[i]=true;
}
while(Q.size())
{
int u=Q.front();
Q.pop();
st[u]=false;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
double w=wf[u]-mid*wt[i]; //这里必须用double确保精度
if(dis[j]<dis[u]+w)
{
dis[j]=dis[u]+w;
cnt[j]=cnt[u]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
st[j]=true;
Q.push(j);
}
}
}
}
return false;
}
int main()
{
memset(h,-1,sizeof h);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>wf[i];
int a,b,c;
while(m--)
{
cin>>a>>b>>c;
add(a,b,c);
}
double l=0,r=1000;
while(r-l>1e-4)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2lf\n",r);
return 0;
}