2022牛客#2 D 二分+正负环的判断

题意:

nnn个物品,mmm组关系,每组关系以四个整数给出a,b,c,da,b,c,dabcd,代表aaabbb物品可以换取cccddd物品,现在存在某种关系,使得一个物品AAA可以通过反复置换其他物品,最后置换回AAA时数量大于1,为了平衡,决定找出一个常数w∈[0,1]w\in[0,1]w[0,1],使原本的关系变为

aaabbb物品可以换取kckckcddd物品,避免原本不平衡的情况出现。找出最大的符合条件的kkk

方法:

首先,如果能通过置换其他物品置换回本身,那么这个图就一定存在环。我们把关系抽象成边,也就是b→db\rightarrow dbd,边权为ca\frac{c}{a}ac,也就是一个bbb能换ca\frac{c}{a}acddd,我们只需要判断存不存在一个环,使得各边之积>1>1>1,就可以判断这个图是否符合条件。那么如何构造出新图,可以二分kkk,然后检查这个图,需要注意的是,边权变为原来的kkk倍。

如何判断图是否存在环使各边之积大于1呢?可以用bellman−fordbellman-fordbellmanford算法,但是由于浮点数精度较差,并且www较小,考虑转化为对数,因为一个很小的整数的对数是一个很大的负数,因此可以转换为是否存在一个环,各边之和大于000bellman−fordbellman-fordbellmanford判负环反着来即可。

#include<bits/stdc++.h>
#define ll long long
#define fr first
#define se second
#define endl '\n'
using namespace std;

struct way
{
    int from,to;
    double w;
}edge[500005];

int n,m;
double dis[10005],eps=1e-6;

bool check(double k)
{
    //返回是否没有正环
    for(int i=1;i<=n;i++) dis[i]=0;
    k=log(k);//每个边的长度加上一个k
    int cnt=0;
    while(1)
    {
        bool flag=false;
        for(int i=1;i<=m;i++)
        {
            int u=edge[i].from,v=edge[i].to;
            double w=edge[i].w;
            if(dis[u]+w+k>dis[v])
            {
                flag=true;
                dis[v]=dis[u]+w+k;
            }
        }
        if(!flag) break;
        cnt++;
        if(cnt>n+10) return false;//出现环了
    }
    return true;
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        edge[i].from=b;
        edge[i].to=d;
        edge[i].w=log(1.0*c/a);
    }
    double l=0,r=1,ans=0;
    while(r-l>eps)
    {
        double mid=(l+r)/2;
        if(check(mid)){
            ans=max(ans,mid);
            l=mid;
        }
        else r=mid;
    }
    printf("%.10lf",ans);
    return 0;
}

也可以使用spfa判断,但使用spfa需要注意图可能不连通,所以每个点都需要入队

#include<bits/stdc++.h>
#define ll long long
#define fr first
#define se second
#define endl '\n'
using namespace std;

struct way
{
    int to,next;
    double w;
}edge[200005];
int cnt,head[100005];

void add(int u,int v,double w)
{
    edge[++cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt;
}

int n,m;
queue<int>q;
bool in[100005];
int t[100005];
double dis[100005],eps=1e-6;

bool check(double k)
{
    //是否有正环
    k=log(k);
    for(int i=1;i<=n;i++)
    {
        dis[i]=-172875082;
        in[i]=false;t[i]=0;
    }
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++)
    {
        q.push(i);in[i]=true;
        dis[i]=0;t[i]++;
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();in[u]=false;
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            double w=edge[i].w;
            if(dis[u]+w+k>dis[v])
            {
                dis[v]=dis[u]+w+k;
                if(!in[v])
                {
                    q.push(v);in[v]=true;
                    if(++t[v]>=n) return true;
                }
            }
        }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(b,d,log(1.0*c/a));
    }
    double l=0,r=1,ans=0;
    while(r-l>eps)
    {
        double mid=(l+r)/2;
        if(!check(mid)){
            ans=max(ans,mid);
            l=mid;
        }
        else r=mid;
    }
    printf("%.10lf",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值