SPFA找负环

SPFA求负环初始化可以为任意值

方法一:可以思考一下如果没有负环情况下,最多会松弛多少次,再观察一下松弛操场的特点,发现每次这个操作都是引入一个点来更新长度,显然一个路径不包含重复的点,那么如果松弛了超过n-1次,则必然有一个点被重复引入来松弛,那就不是路径了,那么就有负环,本质原因一条最短路的得出不会松弛(也就是加入点的个数参与更新路径)超过n-1次。这是很显然的,至于n或者n-1,影响不大,因为如果有负环,那么一直经过这个环,结果一直都会小,也就是说会松弛无数次,超过n-1就是无数次了呗。你取一个比n-1多大的值都行,不影响结果准确性,但当然太大就对效率有点影响

1.虫洞 Wormholes

信息学奥赛一本通(C++版)在线评测系统

 裸求负环

#include <bits/stdc++.h>
using namespace std;
const int N=510,M=5210;
int n,m1,m2;
int h[N],e[M],w[M],ne[M],idx;
int dis[N];
int q[N],cnt[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++;
}
bool spfa()
{
    int hh=0,tt=0;
    memset(dis,0x3f,sizeof dis);
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    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(dis[j]>dis[t]+w[i])
            {
                dis[j]=dis[t]+w[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()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n>>m1>>m2;
        memset(h,-1,sizeof h);
        idx=0;
        while(m1--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c),add(b,a,c);
        }
        while(m2--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,-c);
        }
        if(spfa()) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

2.观光奶牛(01分数规划+spfa判断负环)

361. 观光奶牛 - AcWing题库

#include <bits/stdc++.h>
using namespace std;
const int N=1010,M=5010;
int n,m;
int wf[N];
int h[N],e[M],wt[M],ne[M],idx;
double dis[N];
int q[N],cnt[N];
bool st[N];
void add(int a,int b,int c)
{
    e[idx]=b,wt[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool check(double mid)
{
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    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(dis[j]<dis[t]+wf[t]-mid*wt[i])
            {
                dis[j]=dis[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()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++)
        cin>>wf[i];
    while(m--)
    {
        int a,b,c;
        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("%.2f\n",r);
    return 0;
}

3.(单词环)Word Rings

#include<bits/stdc++.h>
using namespace std;
const int N=700,M=1e5+10;
int h[N],e[M],ne[M],w[M],idx;
double dist[N];
int n;
bool st[N];
int cnt[N],q[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(double mid)
{
    memset(st,0,sizeof st);//清空上一层状态
    memset(cnt,0,sizeof cnt);//清空上一层状态
    int hh=0,tt=0;
    int count=0;//用来标记运行的次数
    for(int i=0;i<26*26;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]+w[i]-mid)//假如满足01分数规划分析的
            {
                dist[j]=dist[t]+w[i]-mid;
                cnt[j]=cnt[t]+1;//更新走的边数
                if(cnt[j]>=N) return true;//假如大于全部的点了,说明有的点已经重复走过了,有环
                if(++count>2*n) return true;//假如运行超过最大边数的两倍也说明有环
                if(!st[j])//假如不在队列中
                {
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
    return false;//则没有环
}
int main()
{
   char str[1010];
   while(scanf("%d",&n),n)
   {
       memset(h,-1,sizeof h);
       idx=0;
       for(int i=0;i<n;i++)
       {
        scanf("%s",str);
         int len=strlen(str);
         if(len<2) continue;
         int a=(str[0]-'a')*26+(str[1]-'a'),b=(str[len-2]-'a')*26+(str[len-1]-'a');//把两个字母对应到26进制中
         add(a,b,len);//把a->b连接起来边权是串的长度
       }
       if(!spfa(0))//假如0都不通过则就是没环了
       {
           puts("No solution");
           continue;
       }
       double l=0,r=1000;
       while(r-l>1e-4)//二分
       {
           double mid=(l+r)/2;
           if(spfa(mid)) l=mid;//假如满足
           else r=mid;
       }
       printf("%lf\n",l);
   }
	return 0;
}

栈优化就是把队列变成栈就行了

#include<bits/stdc++.h>
using namespace std;
const int N=700,M=1e5+10;
int h[N],e[M],ne[M],w[M],idx;
double dist[N];
int n;
bool st[N];
int cnt[N],q[N];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(double mid)
{
    memset(st,0,sizeof st);//清空上一层状态
    memset(cnt,0,sizeof cnt);//清空上一层状态
    int tt=0;
    for(int i=0;i<26*26;i++) q[tt++]=i,st[i]=true;//把所有点入队
    while(tt!=0)
    {
        int t=q[--tt];
        st[t]=false;//标记这个点出队了
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]<dist[t]+w[i]-mid)//假如满足01分数规划分析的
            {
                dist[j]=dist[t]+w[i]-mid;
                cnt[j]=cnt[t]+1;//更新走的边数
                if(cnt[j]>=N) return true;//假如大于全部的点了,说明有的点已经重复走过了,有环
                if(!st[j])//假如不在队列中
                {
                    q[tt++]=j;
                    st[j]=true;
                }
            }
        }
    }
    return false;//则没有环
}
int main()
{
   char str[1010];
   while(scanf("%d",&n),n)
   {
       memset(h,-1,sizeof h);
       idx=0;
       for(int i=0;i<n;i++)
       {
        scanf("%s",str);
         int len=strlen(str);
         if(len<2) continue;
         int a=(str[0]-'a')*26+(str[1]-'a'),b=(str[len-2]-'a')*26+(str[len-1]-'a');//把两个字母对应到26进制中
         add(a,b,len);//把a->b连接起来边权是串的长度
       }
       if(!spfa(0))//假如0都不通过则就是没环了
       {
           puts("No solution");
           continue;
       }
       double l=0,r=1000;
       while(r-l>1e-4)//二分
       {
           double mid=(l+r)/2;
           if(spfa(mid)) l=mid;//假如满足
           else r=mid;
       }
       printf("%lf\n",l);
   }
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值