spfa判断负环的应用

简介: spfa判断负环的应用

 


1.虫洞 Wormholes(裸spfa判断负环问题)

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

1. #in#include<bits/stdc++.h>
using namespace std;
int n,m,W;
const int N=510,M=5210;
int h[N],e[M],ne[M],w[M],idx;
int dist[N],cnt[N],q[N];//cnt记录是某个点经过的边数
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()
{
    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++];
        st[t]=false;//标记这个点已经不在队列中了
        if(hh==N) hh=0;//循环数组
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])//假如距离比他小
            {
                dist[j]=dist[t]+w[i];//更新距离
                cnt[j]=cnt[t]+1;//更新所走过的边数
                if(cnt[j]>=n) return true;//假如边数大于等于n,说有环的存在,而且我们是按照最小距离更新,所有是负环
                if(!st[j])//假如不在队列中则加到队列中
                {
                    q[tt++]=j;
                    st[j]=true;
                    if(tt==N) tt=0;
                }
            }
        }
    }
    return false;//反之没负环
}
void solve()
{
    memset(h,-1,sizeof h);//初始化
    idx=0;
    cin>>n>>m>>W;
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    while(W--)//假如是虫洞,就建立负边
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,-c);
    }
    if(spfa()) puts("YES");//假如有负环
    else puts("NO");
}
int main()
{
    int T;
    cin>>T;
    while(T--) solve();
  return 0;
}

栈优化

#include<bits/stdc++.h>
using namespace std;
int n,m,W;
const int N=510,M=5210;
int h[N],e[M],ne[M],w[M],idx;
int dist[N],cnt[N],q[N];//cnt记录是某个点经过的边数
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()
{
    memset(st,0,sizeof st);//情况上一层的状态
    memset(cnt,0,sizeof cnt);//情况上一层的状态
    int tt=0;
    for(int i=1;i<=n;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])//假如距离比他小
            {
                dist[j]=dist[t]+w[i];//更新距离
                cnt[j]=cnt[t]+1;//更新所走过的边数
                if(cnt[j]>=n) return true;//假如边数大于等于n,说有环的存在,而且我们是按照最小距离更新,所有是负环
                if(!st[j])//假如不在队列中则加到队列中
                {
                    q[tt++]=j;
                    st[j]=true;
                }
            }
        }
    }
    return false;//反之没负环
}
void solve()
{
    memset(h,-1,sizeof h);//初始化
    idx=0;
    cin>>n>>m>>W;
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    while(W--)//假如是虫洞,就建立负边
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,-c);
    }
    if(spfa()) puts("YES");//假如有负环
    else puts("NO");
}
int main()
{
    int T;
    cin>>T;
    while(T--) solve();
  return 0;
}

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

361. 观光奶牛 - AcWing题库

朴素spfa

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1010,M=5010;
int h[N],e[M],ne[M],idx;
double dist[N],wf[N],wt[M];
int cnt[N],q[N];//cnt记录是某个点经过的边数
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 spfa(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(dist[j]<dist[t]+wf[t]-mid*wt[i])//假如负和01分数规划分析的
            {
               dist[j]=dist[t]+wf[t]-mid*wt[i];//则更新
                cnt[j]=cnt[t]+1;//更新所走过的边数
                if(cnt[j]>=n) return true;//假如边数大于等于n,说有环的存在,而且我们是按照01分数规划更新,所有是负环
                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;//分析最大可能是1000,最小是0
    while(r-l>1e-4)//二分答案,因为要输出两位小数,则精度得是两位小数,则二分则是多两位是4位
    {
        double mid=(l+r)/2;
        if(spfa(mid)) l=mid;
        else r=mid;
    }
    printf("%.2lf\n",l);
  return 0;
}


栈优化

#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1010,M=5010;
int h[N],e[M],ne[M],idx;
double dist[N],wf[N],wt[M];
int cnt[N],q[N];//cnt记录是某个点经过的边数
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 spfa(double mid)
{
    memset(st,0,sizeof st);
    memset(cnt,0,sizeof cnt);
    int tt=0;
    for(int i=1;i<=n;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]+wf[t]-mid*wt[i])//假如负和01分数规划分析的
            {
               dist[j]=dist[t]+wf[t]-mid*wt[i];//则更新
                cnt[j]=cnt[t]+1;//更新所走过的边数
                if(cnt[j]>=n) return true;//假如边数大于等于n,说有环的存在,而且我们是按照01分数规划更新,所有是负环
                if(!st[j])//假如不在队列中则加到队列中
                {
                    q[tt++]=j;
                    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;//分析最大可能是1000,最小是0
    while(r-l>1e-4)//二分答案,因为要输出两位小数,则精度得是两位小数,则二分则是多两位是4位
    {
        double mid=(l+r)/2;
        if(spfa(mid)) l=mid;
        else r=mid;
    }
    printf("%.2lf\n",l);
  return 0;
}

3.(单词环)Word Rings

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

这题建图很重要,按照前两个字母与后两个来建图然后前两个字母指向后两个字母边权为该字符串的长度

法一:还有建好图还得spfa假如直接跑就容易wa,这时我们可以加个优化,也就是做题经验,当更新次数超过最大输入的边数的2倍,我们就可以认为是有负环的

法二:还有一种优化方法是把循环队列换成栈,这样更快找到负环

#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;
}



相关文章
|
7月前
|
存储
【洛谷 P1255】数楼梯 题解(递归+记忆化搜索+高精度)
这是一个使用动态规划解决的“数楼梯”问题。给定楼梯数`N`,求不同上楼方式的数量。程序通过递归函数`f()`计算,其中`f(x) = f(x - 1) + f(x - 2)`,初始条件`f(1) = 1`,`f(2) = 2`。考虑到数据规模可能很大,使用了高精度加法运算。样例输入`4`,输出`5`。代码中定义了一个存储中间结果的向量`mem`,并提供了一个用于显示高精度数的`printv()`函数。
122 0
|
8月前
|
算法
DAY-7 | 牛客-BM21 寻找旋转数组的最小元素:二分法分治思想真的很可靠
这是一个关于编程题目的摘要:题目是牛客网上的&quot;BM21 旋转数组的最小数字&quot;,要求找到旋转数组中的最小数字。题解介绍了使用二分查找算法来解决此问题,因为其时间复杂度优于暴力搜索的线性时间复杂度。二分查找的核心是通过比较中间元素与右端元素的大小,不断缩小搜索范围,最终找到最小值。代码示例展示了如何实现这个算法。总结中强调了二分查找适用于部分有序数组,并指出了解决这类问题的关键在于理解数组的二段单调性。
50 1
|
8月前
|
算法 测试技术 C++
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
|
8月前
|
算法 测试技术 C++
【数论】【分类讨论】【C++算法】1611使整数变为 0 的最少操作次数
【数论】【分类讨论】【C++算法】1611使整数变为 0 的最少操作次数
|
8月前
|
测试技术
【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性
【动态规划】【记忆化搜索】【状态压缩】1681. 最小不兼容性
|
算法 C++
剑指offer(C++)-JZ11:旋转数组的最小数字(算法-搜索算法)
剑指offer(C++)-JZ11:旋转数组的最小数字(算法-搜索算法)
|
算法 程序员
【牛客算法BM2】 链表内指定区间反转
你好,欢迎来到我的博客!作为一名程序员,我经常刷LeetCode题目来提升自己的编程能力。在我的博客里,我会分享一些我自己做过的题目和解题思路,希望能够帮助到大家。今天,我想和大家分享一道挑战性较高的题目,让我们一起来挑战一下吧!作者也是在学习的过程中刷到有意思的题目就会选择与大家分享,并且提供较优解,关于力扣 的文章全部放在博客,如果大家喜欢记得支持作者。🤓
|
算法
BFS——二分图判断
BFS——二分图判断
130 0
BFS——二分图判断
|
机器学习/深度学习 存储 算法
判断二分图
给定一个无向图graph,当这个图为二分图时返回true。 如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。 graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。
118 0
AcWing 246. 区间最大公约数 (gcd性质 线段树)
AcWing 246. 区间最大公约数 (gcd性质 线段树)
115 0
AcWing 246. 区间最大公约数 (gcd性质 线段树)

热门文章

最新文章