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



相关文章
|
11月前
[HDCTF2019]Maze(初识逆向)
[HDCTF2019]Maze(初识逆向)
164 1
|
算法
SPFA算法-最短路-负环
SPFA算法-最短路-负环
82 0
|
6月前
|
存储 算法
最短路之SPFA算法
最短路之SPFA算法
52 0
|
算法
BFS——二分图判断
BFS——二分图判断
118 0
BFS——二分图判断
|
存储 算法
spfa算法的实现
spfa算法的实现
|
算法 C++
spfa
复习acwing算法基础课的内容,本篇为讲解基础算法:spfa,关于时间复杂度:目前博主不太会计算,先鸽了,日后一定补上。
119 0
spfa
Biggest Number深搜
You can start from any square, walk in the maze, and finally stop at some square. Each step, you may only walk into one of the four neighbouring squares (up, down, left, right) and you cannot walk into obstacles or walk into a square more than once.
112 0
Biggest Number深搜
|
算法
spfa 算法模板
spfa 算法模板
93 0