图论--spfa

UVA721 Invitation Cards
题意:有n个成员,各自前往n个站点,然后来回最少花费
分析,最短路 使用spfa,单源最短路

  1. 先求第一个站点到后面所有站点的最小花费
  2. 求各个站点回到第一个站点的最小花费
  3. 第一个很好求解,问题是怎么求从各个站点回到第一个站点,开始想的是每次来一次spfa,后来一想必然超时, ,最后很神奇的发现,可以使用将所有边反向,再求一次第一个站点到所有站点的最小花费就是从各个站点回到第一个站点的花费,将边反向,把各个站点回到第一个站点转化为第一个站点再次去各个站点找寻最小花费
  4. 一共使用了两次spfa
  5. sum开long long
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstdio>
#include<cstring>

using namespace std;

const int N = 1e6+10;
int T,n,m;
int e[N],h[N],ne[N],w[N],idx;
int d[N];
int a[N],b[N],c[N];
bool st[N];

void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

void rebulid()
{
    memset(e,0,sizeof(e));
    memset(ne,0,sizeof(e));
    memset(w,0,sizeof(e));
    memset(h,-1,sizeof(e));
    for(int i=0;i<m;i++)
    {
        add(b[i],a[i],c[i]);
    }
}

void spfa()
{
    queue<int>q;
    memset(d,0x3f,sizeof(d));
    memset(st,0,sizeof(st));
    q.push(1);
    st[1]=true;
    d[1]=0;
    while(q.size())
    {
        int x=q.front();
        q.pop();
        st[x]=false;
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int o=e[i];
            if(d[o]>d[x]+w[i])
            {
                d[o]=d[x]+w[i];
                if(!st[o])
                {
                    st[o]=true;
                    q.push(o);
                }
                //cout<<'i'<<' '<<i<<' '<<o<<' '<<d[o]<<'-'<<x<<'-'<<d[x]<<endl;
            }

        }
    }

}

int main()
{
    scanf("%d",&T);
    while(T--)
    {

        scanf("%d%d",&n,&m);
        memset(h,-1,sizeof(h));
        idx=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a[i],&b[i],&c[i]);
            add(a[i],b[i],c[i]);
        }
        spfa();
        long long t=0;
        for(int i=1;i<=n;i++)
        t+=d[i];

        rebulid();
        spfa();
        for(int i=1;i<=n;i++)
        t+=d[i];

        printf("%lld\n",t);

    }

    return 0;
}

P1931 套利
题意:给出各个币的汇率 是不是可以换到最后回到第一个币大于本身的钱
分析:抽象成图,每一种币是一个节点,汇率是边上的权值,找到一个环并最后环的汇率大于1则可以实现套利
使用spfa判断是否有环,跑一边spfa单源最短路
SPFA 找正环

#include<iostream>
#include<algorithm>
#include<string>
#include<queue>
#include<map>
#include<cstring>
#include<cstdio>

using namespace std;

const int N = 50;
int n,m;
double d[N],w[10001];
int e[10001],h[N],ne[10001];
bool vis[N];
int idx=0;
map<string,int>ma;
int Times[N];

int c=1;

void add(string a,string b,double p)
{
    e[idx]=ma[b];
    w[idx]=p;
    ne[idx]=h[ma[a]];
    h[ma[a]]=idx++;
}

bool spfa(int st)
{
    queue<int>q;
    memset(d,0,sizeof(d));
    memset(vis,0,sizeof(vis));
    memset(Times,0,sizeof(Times));//环

    q.push(st);
    d[st]=1.0;
    Times[st]=1;
    vis[st]=true;

    while(q.size())
    {
        int x=q.front();
        q.pop();
        vis[x]=false;
        for(int i=h[x];i!=0;i=ne[i])
        {
            int j=e[i];
            if(d[j]<d[x]*w[i])
            {
                d[j]=d[x]*w[i];
                if(!vis[j])
                {
                    vis[j]=true;
                    q.push(j);
                    ++Times[j];
                    if(Times[j]>=n)return true;

                }
                //cout<<'j'<<' '<<d[j]<<endl;
            }
        }
    }
    //cout<<d[en]<<endl;
    return false;
}

int main()
{
    while(scanf("%d",&n))
    {
        if(n==0)break;
        idx=1;
        memset(h,0,sizeof(h));
        memset(e,0,sizeof(e));
        memset(ne,0,sizeof(e));
        memset(w,0,sizeof(e));

        for(int i=1;i<=n;i++)
        {
            string a;
            cin>>a;
            ma[a]=i;
            //cout<<a<<' '<<ma[a]<<endl;
        }
        scanf("%d",&m);
        while(m--)
        {
            string a,b;
            double p;
            cin>>a>>p>>b;
            add(a,b,p);
        }
        int flag=0;
        for(int i=1;i<=n;i++)
        {
            if(spfa(i))
            {
                printf("Case %d: Yes\n",c++);
                flag=1;
                break;
            }
        }
        if(!flag) printf("Case %d: No\n",c++);
    }

    return 0;
}

POJ - 1860 Currency Exchange
题意:货币进行转化,小明有s的v这么多钱,问能不能通过汇率转化使钱增多
分析:首先一定存在环使ta可以回来,每一条吧边都是双向的,其次判断是不是最终钱变多了,每一次从a到b,汇率为c,手续费为d
若存在正环,就可以实现套利(钱越换越多)
SPFA找正环

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>

using namespace std;

const int N = 1500;
int h[N],idx,e[N],ne[N];
double w[N],y[N];
double d[N];
int Time[N];
bool vis[N];

int n,m,s;
double v;

void add(int a,int b,double c,double d)
{
    e[idx]=b;
    w[idx]=c;
    y[idx]=d;
    ne[idx]=h[a];
    h[a]=idx;
    idx++;
}

int spfa()
{
    memset(d,0,sizeof(d));
    memset(vis,0,sizeof(vis));
    memset(Time,0,sizeof(Time));

    queue<int>q;
    d[s]=v;
    vis[s]=true;
    q.push(s);
    Time[s]++;

    while(q.size())
    {
        int t=q.front();
        q.pop();
        vis[t]=false;

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]<(d[t]-y[i])*w[i])
            {
                d[j]=(d[t]-y[i])*w[i];
                if(!vis[j])
                {
                    Time[j]++;
                    if(Time[j]>=n)return 1;
                    vis[j]=true;
                    q.push(j);
                }


            }
        }
    }
    return 0;
}

int main()
{
    scanf("%d%d%d%lf",&n,&m,&s,&v);
    memset(h,-1,sizeof(h));

    while(m--)
    {
        int a,b;
        double x1,y1,x2,y2;
        scanf("%d%d%lf%lf%lf%lf",&a,&b,&x1,&y1,&x2,&y2);
        add(a,b,x1,y1);
        add(b,a,x2,y2);
    }

    if(spfa())printf("YES\n");
    else printf("NO\n");

    return 0;
}

/*
3 2 1 20.0
1 2 1.00 1.00 1.00 1.00
2 3 1.10 1.00 1.10 1.00
YES
3 2 1 10.0
1 2 1.00 1.00 1.00 1.00
2 3 1.10 1.00 1.10 1.00
NO
*/

POJ - 3259 Wormholes

题意:n个农场,m条路,还有s个黑洞,虫洞可以从一个农场回到另一个农场的前c秒,问能不能找到一条路径能回到一个农场出发之前
分析:每一个农场就是一个节点,每条路是双向边,虫洞是一条a到b的单向负边,问能不能找到一个负环,跑一遍SPFA找负环
ps:多组数据,各个数组初始化,还有idx=0(wa了两发)

#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>

using namespace std;

const int N = 710, M = 1e4+10;

int t,n,m,s;
int d[N];
int h[N],ne[M],w[M],e[M],idx;
bool vis[N];
int Time[N];

void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

void init()
{
    idx=0;//初始化
    memset(h,-1,sizeof(h));
    memset(Time,0,sizeof(Time));
    memset(vis,0,sizeof(vis));
    memset(d,0,sizeof(d));
}

int spfa()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        q.push(i);
        vis[i]=1;
    }

    while(q.size())
    {
        int t=q.front();
        q.pop();
        vis[t]=false;

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                Time[j]=Time[t]+1;
                if(Time[j]>=n)return 1;

                if(!vis[j])
                {

                    vis[j]=true;
                    q.push(j);
                }
            }
        }

    }
    return 0;
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d%d",&n,&m,&s);
        while(m--)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        while(s--)
        {
            int a, b, c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,-c);
        }

        if(spfa())puts("YES");
        else puts("NO");

    }

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值