负环与01分数规划——观光奶牛

01分数规划,简单的来说,就是有一些二元组(si,pi),从中选取一些二元组,使得∑si / ∑pi最大(最小)。

这种题一类通用的解法就是,我们假设x = ∑si / ∑pi的最大(小)值,那么就有x * ∑pi = ∑si ,即∑si  - x * ∑pi= 0。也就是说,当某一个值x满足上述式子的时候,它就是要求的值。我们可以想到枚举……不过再想想,这个可以二分答案。

所以我们直接二分答案,当上述式子>0,说明答案小了,<0则说明答案大了,这样计算即可。

(以上来自网络)

传送门:361. 观光奶牛 - AcWing题库

思路:设环上各点的权值为f[i],环上各边的权值为t[k],由题目可的Σf[i]/Σt[k]==mid,使得mid的值最大,换一下就是找到使得Σf[i]-mid*Σt[k]>0成立的最大mid的值。

找mid的过程就是二分的过程。

f[i]-mid*t[k]含义:点i的权值减去mid乘点i的出边的边权。

对于上面的Σf[i]-mid*Σt[k],建立一个新图使得边权为f[i]-mid*t[k],在新图里面进行找最长路的操作,当mid取值太小时,Σf[i]-mid*Σt[k]会小于0,此时会存在负环,但因为是求最长路,并不会有某一个点的入队次数超过n,最后会返回false,存在正环的时候就会返回true。true说明mid取小了,使二分边界 l =mid ,返回false就使r=mid,最后使得abs(r-l)小于1e-4,可以确保保留两位小数的数值准确。

代码:
 

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1010,M=5010;
int n,m;
int wf[N];
int h[N],wt[M],idx,ne[M],e[M];
double dist[N];
bool st [N];
int q[N],cnt[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,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    memset(dist,0,sizeof dist);
    int hh=0,tt=0;
    for(int i=1;i<=n;i++)
    {
        q[tt++]=i;
        st[i]=true;
    }
    while(hh!=tt)
    {
        int t=q[hh++];
        if(hh==N) hh=0;
        st[t]=false;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]<dist[t]+wf[t]-mid*wt[i])
            {
                dist[j]=dist[t]+wf[t]-mid*wt[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n) return true;
                 if(!st[j])
                {
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
    return false;
}



int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++)
        scanf("%d",&wf[i]);
    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-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、付费专栏及课程。

余额充值