で最短経路テンプレートテンプレートspfaアルゴリズムにおいて別段無限ループが存在することになる、負の重みループなしのグラフにのみ適しています。
次に、いくつかの変更を加えて、spfaアルゴリズムを介して負のリングがあるかどうかを判断します。
SPFAに基づいて、負のリングを見つける一般的な方法:
- 各ポイントがチームに入った回数を数えます。ポイントがチームにn回入った場合、それは負のリングがあることを意味します。
- 現在、各点の最短経路に含まれる辺の数、つまり経路長を数えます。点の最短経路に含まれる辺の数がn以上の場合は、ループ。
以下のアイデアとして、方法2の問題を解決します
。nポイントの最大パス長はn-1であるため、頂点で終了最短経路長が> = nの場合、ループがあることを意味します。
1.では、どの頂点を最短パス長から開始する必要がありますか?
上記の最短経路を見つけるためのテンプレートは、特定の開始点から他のすべての頂点までの最短経路であり、必ずしもすべての点を通過するとは限りません。接続されたグラフの場合、すべての頂点に到達可能であり、すべて通過できます。負のリングがない場合、最短経路があります。接続されていないグラフの場合、一部の頂点に到達dis[k]=INF
できず、到達できません。すべてが通過しました。
負のリングがあるかどうかを判断するときは、グラフ全体をトラバースする必要があり、固定の開始点はありません。接続されたグラフの場合、任意のポイントから開始でき、最終的にはグラフ全体の頂点とエッジを通過できます。接続されていないグラフの場合、接続された各サブグラフの少なくとも1つのポイントを次のように解釈する必要があります。出発点。
各点は最終的にトラバースされるため、すべての頂点を開始点として事前にキューに入れることができます。そのため、接続されたグラフかどうかを判断する問題には、いくつかの接続されたグラフがあります。
開始点sから他のすべての頂点までの最短経路を背景として、負のリングの一般的な傾向を分析します。負のリング(i、k1、k2、k3、...、kn、j)があると仮定します。
sはiに到達でき、次にsは到達できますiが配置されているリングのすべての頂点について、点iはグラフ内の任意の位置であり、dis [i]はs-> iの最短経路重みを表し、knに到達すると、iは、ループを継続するために、新しいdis [i]が元の値よりも小さくなるように再度更新されます。
したがって、連結グラフの任意の点から開始する限り、任意の点に到達できるため、連結グラフに負のリングがあるかどうかを判断できることがわかります。
負のリングの存在のみを求める場合、disの意味は重要ではありません。最短経路のように無限大に初期化してから、開始点を0に指定する必要はありません。つまり、存在しません。 disの初期値に制限し
ます。負のリングがない場合、キューは遅かれ早かれ空になり、最終的には特定の状態で最短パスになる傾向があります。これはdisの答えではありませんが、
負のリングがある場合、キューは空になることはなく、常に負のリングになります。上のループでは、負のループが存在するパスの最短パスは最終的に負の無限大になるため、次のように判断できます。負のループが存在します。
負のループを判断する場合、最短パスの特定の値を見つける必要はありません。更新が多すぎるかどうかを判断するだけです。負のリングがある場合、最短経路長が負の無限大になるポイントがいくつかあるはずです。そうすると、必然的に無限に更新されるため、初期値を割り当てなくても問題ありません。
dis配列がすべて0に初期化されるときの意味を指定できます。
元のグラフに基づいて新しい仮想ソースポイントを作成し、このポイントから他のすべてのポイントに重み0、長さ0の有向エッジを接続します。次に、新しいグラフは接続されたグラフである必要があり、次に元のグラフである必要があります。グラフに負のリングがある新しいグラフに負のリングがあるのと同じです。この時点で、この仮想ソースポイントを開始点として使用できます。(selfからselfまでの距離は0で、disの値はINFです)。仮想ソースポイントをキューに追加してから、最初の反復を実行します。この時点で、dis配列は元のINFから次のように更新されます。 0の場合、図が更新されます。すべてのポイントがキューに挿入されます。
これは、dis arrayの意味であり、グラフ内のすべてのポイントと仮想ソースポイントの間の距離です。
2.負のリングがあるかどうかを判断する方法は?
cnt配列を維持することができます。最初は、すべてのポイントがすべての開始ポイントであると想定してキューに入れられます。したがって、cnt [i] = 0およびcnt = 0のすべてのポイントが開始ポイントであり、複数の開始ポイントが許可されます。 。開始点から、接続された各頂点にもう1つのエッジがあるため、cntは1増加し、ポイントが1増加すると、0ではないため、開始点ではないため、cnt [k ] = xレコードがkです。終点の最短経路が通過する辺の数はxで、始点はcntが0の点ですが、どちらであるかはわかりません。
ループがない場合、n点間に最大でn-1個のエッジがあるため、cnt [x]> = nの場合、グラフに負のループが存在する必要があることを意味します。
前述のようにdisを0に初期化する意味を組み合わせると、cntは特定のポイントから仮想ソースポイントに渡されるエッジの数であることが理解できますが、特別な規制があります。仮想に直接接続されているエッジです。ソースポイントとグラフ内のポイントの長さは0(つまり、エッジはカウントされません)で、重みは0です。
タイトル説明
n個のポイントとm個のエッジを持つ有向グラフが与えられた場合、グラフには複数のエッジと自己ループが存在する可能性があり、エッジの重みは負になる可能性があります。
グラフに負の重みループがあるかどうかを判断してください。
入力形式
最初の行には整数nとmが含まれています。
次のm行のそれぞれには、3つの整数x、y、zが含まれています。これは、点xから点yに有向エッジがあり、辺の長さがzであることを示します。
出力形式
図に負の重みループがある場合は「はい」を出力し、そうでない場合は「いいえ」を出力します。
データ範囲は
1≤n≤2000、
1≤m≤10000、及び
図中の辺の長さの絶対値が10000を超えることはありません。
入力サンプル:
3 3
1 2 -1
2 3 4
3 1 -4
サンプル出力:
はい
コード
#include <iostream>
#include <cstring>
using namespace std;
const int N=2010,M=1e4+10;
int h[N],e[M],ne[M],w[M],idx;
int dis[N],cnt[N];
bool vis[N];
int q[N*N],hh,tt=-1; 队列的大小不能和顶点数一样,tt是一直加的,要开大点,不然空间不够
int n,m; 或者考虑写成循环队列,tt从0开始,hh==tt时队列为空。
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
//不用初始化dis数组了,默认是0就行
for (int i=1;i<=n;i++) q[++tt]=i,vis[i]=true;
//结点编号从1号开始的
while (hh<=tt) {
int v=q[hh++];
vis[v]=false;
for (int i=h[v];i!=-1;i=ne[i]) {
int j=e[i];//v->j的边
if (dis[j] > dis[v]+w[i]) {
dis[j]=dis[v]+w[i];
if (!vis[j]) q[++tt]=j,vis[j]=true;
cnt[j]=cnt[v]+1; //v->j,j经过的边数是是v经过的边数+1,注意别用自加,改变cnt[v]的值
if (cnt[j]>=n) return true; //发现负环,可以退出。
}
}
}
return false;//如果存在负环,肯定退出不出while循环,既然能退出while循环,说明不存在负环。
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
int a,b,c;
for (int i=0;i<m;i++) {
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
spfa()==true?puts("Yes"):puts("No");
//调用函数的同时使用其返回值
return 0;
}
質問:初期化時に、循環キュー
をシミュレートするための配列の記述に習熟していること:hh = tt = 0、またはstlコンテナキューを直接使用する。通常のキューは最初に++で、次に数値が格納され、ttの位置が数値の最後の位置になります。スタックも最初に++で、次に数値が格納されます。循環キューは、最初に数値を格納し、次に++の位置を格納します。ttは、数値が格納される位置です。
int q[N],hh,tt;
bool spfa()
{
for (int i=1;i<=n;i++) {
q[tt++]=i,vis[i]=true;
if(tt==N) tt=0; 一、或者写成取模的形式也行:tt=(tt+1)%N; 下面同理
}
while (hh!=tt) {
int v=q[hh++];
vis[v]=false;
if (hh==N) hh=0; 二、
for (int i=h[v];i!=-1;i=ne[i]) {
int j=e[i];//v->j的边
if (dis[j] > dis[v]+w[i]) {
dis[j]=dis[v]+w[i];
if (!vis[j]) {
q[tt++]=j,vis[j]=true;
if(tt==N) tt=0; 三、
}
cnt[j]=cnt[v]+1; //v->j,j经过的边数是是v经过的边数+1,注意别用自加,改变cnt[v]的值
if (cnt[j]>=n) return true;
}
}
}
return false;
}