SPFA(判负环) + 二分(01分数规划) - Sightseeing Cows - POJ 3621

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 2L1000,2P5000,1f[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])1in

01分数规划 通 常 用 于 求 解 最 高 ′ ′ 性 价 比 ′ ′ 的 问 题 。 通常用于求解最高''性价比''的问题。

我 们 可 以 二 分 最 大 值 m i d , 接 着 判 断 是 否 存 在 某 个 环 满 足 m a x ( ∑ f [ i ] ∑ t [ i ] ) > m i d , 我们可以二分最大值mid,接着判断是否存在某个环满足max(\frac{\sum f[i]}{\sum t[i]})>mid, midmax(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=110001[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], midf[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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值