シングルソース最短_spfa

10212 ワード

SPFA
適用範囲:与えられた図には負の重みエッジが存在し、Dijkstraのようなアルゴリズムは武力を使う場所がなく、Bellman-Fordアルゴリズムの複雑さが高すぎて、SPFAアルゴリズムが役に立ちました.重み付け図Gには負の重み回路が存在しないこと,すなわち最短経路は必ず存在することを約束する.所望の時間複雑度O(ke)は、kがすべての頂点のキューに入る平均回数であり、kが一般的に2以下であることを証明することができる.
負ループの有無の判断:あるポイントがキューに入る回数がN回を超えると負ループが存在する
アルゴリズム擬似コード:
 procedure Shortest-Path-Faster-Algorithm(G, s)
  1    for each vertex v ≠ s in V(G)
  2        d(v) := ∞
  3    d(s) := 0
  4    offer s into Q
  5    while Q is not empty
  6        u := poll Q
  7        for each edge (u, v) in E(G)
  8            if d(u) + w(u, v) < d(v) then
  9                d(v) := d(u) + w(u, v)
 10                if v is not in Q then
 11                    offer v into Q

P 3371【テンプレート】シングルソース最短パス題意:始点から各点までの最短パスを求める
#include
#include
#include
#include
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x7fffffff;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inque[MAXN];//        
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
void spfa_bfs(int st)
{
    memset(inque,0,sizeof(inque));
    dis[st]=0;
    queue que;
    que.push(st);
    int u,v,i;
    while(!que.empty())
    {
        u=que.front();que.pop();
        inque[u]=0;
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inque[v])
                {
                    que.push(v);
                    inque[v]=1;
                }
            }
        }
    }
}
int main()
{
    int n,m,st,u,v,val;
    scanf("%d%d%d",&n,&m,&st);
    fill(dis+1,dis+1+n,INF);
    memset(head,-1,sizeof(head));
    cnt=0;
    for(int i=0;i

Wormholes題意:図は連通しており、負のループ題解を判断する:任意の起点でspfaを行う
#include
#include
#include
#include
using namespace std;
const int MAXN=10010;
const int MAXE=500010;
const int INF=0x3f3f3f3f;
struct Node
{
    int to,next,val;
};
Node edge[MAXE];
int cnt,head[MAXN];
int dis[MAXN],inque[MAXN];
int queNum[MAXN];//    
void addEdge(int u,int v,int val)
{
    edge[cnt].to=v;edge[cnt].val=val;
    edge[cnt].next=head[u];head[u]=cnt++;
}
bool spfa_bfs(int st,int n)
{
    memset(inque,0,sizeof(inque));
    memset(dis,INF,sizeof(dis));
    memset(queNum,0,sizeof(queNum));
    dis[st]=0;
    queue que;
    que.push(st);
    queNum[st]++;
    int u,v,i;
    while(!que.empty())
    {
        u=que.front();que.pop();
        inque[u]=0;
        for(i=head[u];i!=-1;i=edge[i].next)
        {
            v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inque[v])
                {
                    que.push(v);
                    queNum[v]++;
                    if(queNum[v]>n) return true;//   
                    inque[v]=1;
                }
            }
        }
    }
    return false;
}
int main()
{
    int n,m,k,u,v,val,t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&k);
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=0;i

実は、spfaはDFSの形式に書くことができて、ただキューをスタックに変えただけです!同時に、bfs形式のspfaで負のループを判断するのは遅いです.負のループがある場合、ある点にn回エンキューしてから判断しなければなりません.nが大きいと、非常に時間がかかりますが、DFSで改善することができます.DFSは1つの点をスタックに繰り返すのではなく、次の点をスタックに入れるからです.
負のループを判断する方法を見てみましょう.
  • 1、現在のノードがスタック中にあるかどうかをspfaで同時に記録する
  • .
  • 2、ノードが現在のノードによって緩和され得る場合
  • ノードはスタック内にあり、緩和経路にループが現れることを示し、
  • を終了する.
  • そうでない場合、このノードを現在の点としてspfa検索動作
  • を行う.

    しかし、図に負のループがないことを明確に知っていれば、最短経路を求めるだけなのか、BFSのspfaを使うのか、DFSスタックが深すぎて走るのが遅い!
    ここではspfa_を先に示しますdfsは最短のコード(実はすでに負のループを判断することができます)を求めて工事の継続的な問題の意味をスムーズにします:起点の終点を与えて、最短のルートを求めます
    #include
    #include
    #include
    using namespace std;
    const int MAXN=10010;
    const int MAXE=500010;
    const int INF=0x7fffffff;
    struct Node
    {
        int to,next,val;
    };
    Node edge[MAXE];
    int cnt,head[MAXN];
    int dis[MAXN],instack[MAXN];//       
    void addEdge(int u,int v,int val)
    {
        edge[cnt].to=v;edge[cnt].val=val;
        edge[cnt].next=head[u];head[u]=cnt++;
    }
    bool spfa_dfs(int u)
    {
        instack[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!instack[v])
                {
                    if(spfa_dfs(v)) return true;//   
                }
                else return true;//   
            }
        }
        instack[u]=0;
        return false;
    }
    int main()
    {
        int n,m,st,ed,u,v,val;
        while(scanf("%d%d",&n,&m)!=EOF)
        {
            memset(head,-1,sizeof(head));
            cnt=0;
            for(int i=0;i

    Wormholes題意:図は連通しており、負のループ題解を判断する:任意の起点でspfaを行う
    #include
    #include
    #include
    using namespace std;
    const int MAXN=10010;
    const int MAXE=500010;
    const int INF=0x3f3f3f3f;
    struct Node
    {
        int to,next,val;
    };
    Node edge[MAXE];
    int cnt,head[MAXN];
    int dis[MAXN],inStack[MAXN];//       
    void addEdge(int u,int v,int val)
    {
        edge[cnt].to=v;edge[cnt].val=val;
        edge[cnt].next=head[u];head[u]=cnt++;
    }
    bool spfa_dfs(int u)
    {
        inStack[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inStack[v])
                {
                    if(spfa_dfs(v)) return true;//   
                }
                else return true;//   
            }
        }
        inStack[u]=0;
        return false;
    }
    int main()
    {
        int n,m,k,u,v,val,t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d%d",&n,&m,&k);
            memset(head,-1,sizeof(head));
            cnt=0;
            for(int i=0;i

    実は、負のループを判断するだけで、最短のパスを求める必要がなければ、dfsのspfaには最適化の場所があります!まず、負のループが存在する場合、あるエッジが負であることは間違いありません.最初から負のエッジからspfaを行うとします.dfsなら、すぐにこの負のリングを見つけることができ、負のリングを見つけると退出し、このように速くなります.
    では、最初はどうやって負の辺を見つけますか??まずspfaを見てみましょうdfsでは、現在、1回目のdfs拡張が行う、1回目に負のエッジが選択されるようにするには、dis配列を0に初期化し、拡張を行うと、dis[u]=0、dis[v]=0となり、dis[v]>dis[u]+edge[i]となる.val,では辺は必ず負で,それから負の辺を拡張した.すなわち,dis配列は0に初期化される.このように処理すると,最初の拡張は起点に接続された辺権が負の辺にのみ広がる.もちろん,負ループ+最短ループを求める場合はdis配列をこのように初期化することはできないが,重みが負の経路が存在し負ループが存在しない可能性があるため,このような最短ループが合理的であるため,正直にdis配列を∞に初期化する.
    P 3385【テンプレート】負ループ題意:図を与え、負ループがあるか否かを判断する.図は必ずしも連通しているとは限らないので、複数の異なる起点単源最短判定負ループを通過しなければならない.(spfa_bfsはタイムアウトし、spfa_dfsのdis配列は0に初期化しなくてもタイムアウトする)
    #include
    #include
    #include
    using namespace std;
    const int MAXN=200010;
    const int MAXE=200010*2;
    const int INF=0x3f3f3f3f;
    struct Node
    {
        int to,next,val;
    };
    Node edge[MAXE];
    int cnt,head[MAXN];
    int dis[MAXN],inStack[MAXN];//       
    void addEdge(int u,int v,int val)
    {
        edge[cnt].to=v;edge[cnt].val=val;
        edge[cnt].next=head[u];head[u]=cnt++;
    }
    bool spfa_dfs(int u)
    {
        inStack[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inStack[v])
                {
                    if(spfa_dfs(v)) return true;//   
                }
                else return true;//   
            }
        }
        inStack[u]=0;
        return false;
    }
    int main()
    {
        int n,m,u,v,val,t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d",&n,&m);
            memset(head,-1,sizeof(head));
            cnt=0;
            for(int i=0;i=0) addEdge(v,u,val);
            }
            memset(inStack,0,sizeof(inStack));
            memset(dis,0,sizeof(dis));
            bool flag=false;
            for(int i=1;i<=n;i++)
            {
                if(spfa_dfs(i))
                {
                    flag=true;
                    break;
                }
            }
            if(flag) printf("YE5
    "); else printf("N0
    "); } }

    また、グローバル変数flagを用いたdfsをもう一つあげましょう.実は上と同じです.
    #include
    #include
    #include
    using namespace std;
    const int MAXN=200010;
    const int MAXE=200010*2;
    const int INF=0x3f3f3f3f;
    struct Node
    {
        int to,next,val;
    };
    Node edge[MAXE];
    int cnt,head[MAXN];
    int dis[MAXN],inStack[MAXN];//       
    void addEdge(int u,int v,int val)
    {
        edge[cnt].to=v;edge[cnt].val=val;
        edge[cnt].next=head[u];head[u]=cnt++;
    }
    bool flag;
    void spfa_dfs(int u)
    {
        inStack[u]=1;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].val)
            {
                dis[v]=dis[u]+edge[i].val;
                if(!inStack[v])
                {
                    spfa_dfs(v);
                    if(flag) return;//   
                }
                else
                {
                    flag=true;
                    return;//   
                }
            }
        }
        inStack[u]=0;
    }
    int main()
    {
        int n,m,u,v,val,t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d%d",&n,&m);
            memset(head,-1,sizeof(head));
            cnt=0;
            for(int i=0;i=0) addEdge(v,u,val);
            }
            memset(inStack,0,sizeof(inStack));
            memset(dis,0,sizeof(dis));
            flag=false;
            for(int i=1;i<=n;i++)
            {
                spfa_dfs(i);
                if(flag) break;
            }
            if(flag) printf("YE5
    "); else printf("N0
    "); } }