spfa魔法の変更方法
( \(更新\) \(\上の) \(2019年8月13日\) )
参考文献:
ETOのメンバーの小さなシリーズコレクションの問題への解決策
余談:
このブログのトピックについて、私は......脱出する光の速度(少数だと思います:
それらのものはBellmanFord上の生まれ変わりのspfaになります
OIグラフ理論効率至上主義へようこそ
RE:ゼロベースのグラフ理論の生活
トレリスを実行するだけでなく、菊の地図を詰まって、あなたがそれを好きなspfa
科学的なチーム優れたspfa
私は何の問題は本当にspfaを最適化していません
話題に:
まずルック\(spfa \)それ、\(spfa \)が立っている\(最短\) \(パス\) \(最速\) \(Mathimatics-数値アルゴリズム\) 、実際には、\(ベルマン\) - \(浅瀬\)アルゴリズムプラスキューの最適化。
その時の複雑さが安定していない、最速で到達することができます(O(1)\)\、への最も遅いカードになります\(ベルマン\) - \(フォードが\)です\(O(nm)を\)
あなたが今日学んだことをまとめると\(spfaの\)様々な魔法の変更:
フロント:読み取りの最適化
そうである癌は、トピックカードの時間をするので、私たちは優れた(必ずしも最適化されていない出力)を読む必要があります
レプリケーションで使用される標準タグ:()
inline int read()
{
int fu=1,x=0;char o=getchar();
while(o<'0'||o>'9'){if(o=='-')fu=-1;o=getchar();}
while(o>='0'&&o<='9'){x=(x<<1)+(x<<3)+(o^48);o=getchar();}
return x*fu;
}
、spfa記録パス
友人にスターを付けるには、私はこの男前方StarRamマップなので、最初に書かれたレコードの特に好きですので、唯一の方法
私たちは、使用\(前\)記録ノードの前駆体配列を。緩和が成功すると、我々は、記録された\([V]は= uの事前 \) だけでスタートDFSは端から出力を逆に、最終的な出力(またはコール)だけで罰金。
実際には、この方法はまた、ダイクストラ上で使用することができます......
ラベリング処理への最初:
#include<queue>
#include<cstdio>
#include<iostream>
using namespace std;
struct Edge
{
int dis,nst,to;
}edge[200010];
int n,m,head[100010],dis[100010],vis[100010],cnt,pre[100010];
void add(int a,int b,int c)
{
edge[++cnt].nst=head[a];
edge[cnt].to=b;
edge[cnt].dis=c;
head[a]=cnt;
}
void print(int x)//找路函数
{
if (pre[x]==-1)//找到了起始点
{
printf("%d",x);//输出
return ;
}
print(pre[x]);
printf("-->%d",x);//输出下一个点
return;
}
void spfa()//标准的spfa
{
queue<int>q;
for(int i=1;i<=n;i++)
{
dis[i]=0x7fffffff;
vis[i]=0;
pre[i]=-1;
}
q.push(1);
vis[1]=1;
dis[1]=0;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to;
pre[v]=u;记录v的前驱为u
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
if(!vis[v])q.push(v),vis[v]=1;
}
}
}
}
int main()
{
int a,b,c;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
spfa();
print(n);//找从1到n的最短路径
printf("\n%d",dis[n]);//并输出距离
return 0;
}
二、spfa最短レコード番号
我々は、I CNTアレイを用いて各点に開始点から最短経路の数を記録しました。カウント数を達成するために、私たちはする必要があります(spfa \)を\以下の改善を行うために:
とき(DIS [U] + E [ U] [i]はDIS [i]は= \)\ 、私たちが作る\(CNTを[I] + = CNT [U] \)
とき\(DIS [i]は> DIS [i]の[U] + E [U] \) 、我々は作る\(CNT [i]は= CNTを [U] \)
場合にのみ、\(VIS [I] == 0 \) と\(CNT [i]が!= 0 \) 、私たちは、その後、Vをキューに入れられました
実行キューの空のノートCNTアレイ内の各ポイントの後、それ以外の場合は、レコードを複製します
一方の出発点に検索がnである場合、検索は、それをスキップします、そうでない場合は、最後のCNT [n]は、直接クリアされます
独自のコードと併せて理解されてください。
void spfa()
{
queue <int> q;
for(int i=1;i<=n;i=-~i)//这里的i=-~i就相当于i++
{
dis[i]=1e9;
vis[i]=0;
}
dis[1]=0;vis[1]=1;cnt[1]=1;
q.push(1);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
if(u==n)continue;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to,w=edge[i].dis;
if(dis[v]==dis[u]+w)
cnt[v]+=cnt[u];
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
cnt[v]=cnt[u];
}
if(!vis[v]&&cnt[v])
{
vis[v]=1;
q.push(v);
}
}
cnt[u]=0;
}
}
三、spfa没収リング
右は、マイナス側に処理できるようにするには、\(ベルマン\) - \(フォード\)アルゴリズムの最大の利点を。その最適化として、\(SPFA \)はまた、没収リング機能を継承しています。私達はちょうど必要な\(CNT \)チームとチームへの配列の各ポイントのレコード数、数はnよりも大きい場合は、負のリングがなければならないが、あなたは直接抜け出すことができます
ボードコード:
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
struct Edge
{
int dis,nst,to;
}edge[10010];
int n,m,head[5000],dis[5000],vis[5000],cnt,num[5000];
void add(int a,int b,int c)
{
edge[++cnt].nst=head[a];
edge[cnt].to=b;
edge[cnt].dis=c;
head[a]=cnt;
}
int read()
{
int a=0,b=0;char o;
while((o=getchar())!='\n'&&o!=' ')
{
if(o=='-')b=1;
else if(o>'9'||o<'0')continue;
else {a=a*10;a+=o-'0';}
}
if(b==0)return a;
else return -a;
}
void spfa(int s)
{
queue <int> q;
for(int i=1;i<=n;i++)
{
dis[i]=0x7fffffff;
vis[i]=0;
}
dis[s]=0;
vis[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();vis[u]=0;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to;
if(edge[i].dis+dis[u]<dis[v])
{
dis[v]=dis[u]+edge[i].dis;
num[v]++;
if(num[v]>n){printf("YE5\n");return;}
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
printf("N0\n");
}
int main()
{
int t;
t=read();
for(int i=1;i<=t;i++)
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int a,b,c;
a=read();b=read();c=read();
add(a,b,c);
if(c>=0)add(b,a,c);
}
spfa(1);
for(int i=0;i<=n;i++)
head[i]=0,num[i]=0;
cnt=0;
}
return 0;
}
四、spfaは最長の道を見つけます
差動制約アルゴリズムを勉強するとき、私たちは常に、最も長い道を求めているケースが発生しました。以来\(ダイクストラ\)貪欲な性質、最長のを見つけるためにそれを使用することは最良の選択ではありませんので、我々は使用する必要があります\(SPFA \を)。
しかし、言われて、あなただけの場合は最長の道路緩和、トピックを判断し、あなたが人々の頭を描く許可すれば、通常のトピックを持っていたし、あなたの周りに回転するように人々は、データカードにあなたを構築します(T \)\飛びます
私たちは、知っている\(SPFA \)は、負の右側を扱うことができるので、我々は右側反対の数を取り、その後、最短数に最終的な出力の向かい、最短経路を見つけることができました
ここでは、コード(あります補うために時間を見つけて、書きません)
五、spfaは短絡を見つけます
厳密に短絡を求めてspfa
主なアイデアは、次のとおりです。
最短点X最短点yを更新することができる場合、その後、点x時間は短絡点が更新最短点xのy最短ポイントとY短絡更新します。
X点の最短経路は、Y最短点、X最短更新時間短絡点yと、その後のポイントを更新することができない場合。
コアコード:
//dis是最短路数组,dist是次短路数组
void spfa()
{
queue <int> q;
for(int i=1;i<=n;i++)
{
dis[i]=1e9;
dist[i]=1e9;
vis[i]=0;
}
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to,w=edge[i].dis;
if(ju[v])continue;
if(dis[v]>=dis[u]+w)
{
dist[v]=min(dist[v],dist[u]+w);
dis[v]=dis[u]+w;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
else if(dist[v]>dis[u]+w)
{
dist[v]=dis[u]+w;
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
}
非厳格な短絡を求めてspfa
これは協力すべきである\(spfa \)記録パスを達成するために。一般的な考えはである:開始とサイド\(SPFA \)最短画像を見つけるために、及び、最大出力に対応する最短経路を見つけるために、各グラフの最短辺の各トラバースを削除請求取ることができます
コアコード:
int spfa()
{
queue <int> q;
for(int i=1;i<=n;i++)
{
dis[i]=0x3f3f3f3f;
vis[i]=0;
}
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to;
if(!a[u][v]&&dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
if(!flag)pre[v]=u;//只在第一遍spfa时记录路径
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return dis[n];
}
void dfs(int u)
{
if(pre[u]==0)return;
a[u][pre[u]]=1;
a[pre[u]][u]=1;
rec[++num]=spfa();
a[u][pre[u]]=0;
a[pre[u]][u]=0;
dfs(pre[u]);
}
余談:短絡をk個
需要kとしてA *アルゴリズムを使用して短絡、および\(spfa \は)関係は、このブログでそれについて話をするものではありません。あなたが興味を持っている場合は、することができますが、会場の説明小さなコレクション-第六爆弾三タイトル
spfaための第六に、最適化とハック
このセクションでは、リファレンス:spfaの形而上学キュー最適化方法とどのように死んだSPFAアルゴリズムには、この引数を見て?
フロントの説明:すべての複雑さをログに記録するプライオリティキューを維持しながら、できるだけ近いspfaキュープライオリティキューspfaに最適化されているので、ハックの複雑さよりも低くなければなりません。トーキングは、限り、あなたは人を疑問視するカードとして、関係なく、あなたがspfa形而上学的な方法を最適化するもの、ではありませんspfaカードとすることができます。だから、非負の重み付きグラフとspfaを書きしないようにしよう......
しかし、結局、spfaトピックで、これらの最適化は、私もそれについて話します
1、SLFの最適化
SLFの最適化は、使用の両端キューを最適化することである\(ベルマン\) - (フォード\)\尾が大きく中に挿入されている場合、各ノードDISエンキューおよびキューのDISヘッドとを比較し、または最初のチームに挿入されます。
if(q.size()&&dis[q.front()]<dis[v])q.push_back(v);
else q.push_front(v);
ハック:右のいくつかの側面と一緒に縛ら小さな側鎖に菊を入力するためのアルゴリズムを複数回だますことができる、メソッドのデイジーチェーンセットを使用します
2、LLLの最適化
DIS [U]より大きく、それは最初のチーム繰り返し決意要素を取って、尾部にポップアップ表示されますがされるまで実行される場合、各要素は、DIS [U]と平均キューDISを比較し、UをデキューするためプレゼンスDIS [x]の平均値よりも小さいです
while (dis[u]*cnt > sum)
{
q.pop();
q.push(u);
u = q.front();
}
ハック:故障LLLになるように、1に巨大なエッジ重みを結びます
3、MACFの最適化
最初に\([L、R] \ ) ノードを訪問したときに、最初のチームに入れて、または尾へ。通常取ら\(L = 2、R = \ SQRT {V} \)
mcfx最適化の原理は次のとおりです。ノードの離脱などのエッジを超えるほとんどが唯一の解決策一度に更新することができます(それは、複数のノードを介してあなたを得るために人々アウトのために問題外である場合は、この時点では意味しており、それぞれ更新しますその結果、それは、キュー内での優先度を削減するトピックを知っているし、人々はあなたにこのカードのポイントを使用して、あなたはまた、ほとんどに最初にそれを持参してください)菊の図のルートと同様に、反復特に長いです最初の更新は、それは確かに十分ではありません
ハック:図グリッドを介して効果的な、しかしカードの図は、デイジーフライことが
4、SLF +フォールトトレラントの最適化
私たちは、フォールトトレラントの値を定義\(ヴァル\)を、ときに満足\(DIS [今]> DIS [q.front()] +ヴァル\) 尾から挿入、または最初のチームから挿入されています。
設定\(W \)エッジの重みの和され、\(ヴァル\)一般的に\(\ SQRT {W} \ )
SLFのフォールトトレランスは、プログラムがシミュレーテッドアニーリングに似局所最適解へと落ちないことができます
ハック:小さな言葉の右側とは何か良い方法は思えなかったので、カードは、カードSLFの練習法、およびより良い多額開くための右側である場合には、\を(10 ^ {12} \)
5.SLF +スワップの最適化
距離はキューのテール・ヘッドよりも大きい場合たびキューの変更、スイッチは端へ。
この最適化は、通常のホットSLFよりも速くすることができ、なぜ私もあまり理解できません
ハック:SLFカード類似するプラグイン誘起ノード
6.無作為化、最適化(ヨーロッパでRpとガスニーズ)
これは3つの方法があります。
- サイドランダムな順序:ランダム破壊エッジの後、あなたのspfaに読み込まれる(random_shuffle機能を使用することができます)
- ランダムキュー:各ノードをエンキュー、チームからの最初のチームに0.5の確率、尾をエンキューするために0.5から確率。
- 確率的な最適化バージョンをキュー:チームへの回ごとにx個の後に、待ち行列要素がランダムに破壊しました。
私は非UAEのこの種のが好きか、それを忘れて
七、最小限のコストフローのspfa
最小コスト最大フローアルゴリズムEK
+増強パスをspfa EKアルゴリズム。
構築し、その後、実行する権利の当社のコスト側面図\(SPFA \) 。スラックが成功した場合spfaにおいて、我々ダウンできるだけノード水、及び流路が記録されている(\(予備[V] = U \) )。
増強パスを見ながら仕上げSPFA後、ダウン以前に記録された経路は、シンクからソースポイントまでさかのぼります
総コストが最小(電流経路の合計量)*(T秒の最短経路)であり、そして
あなたが会場と読むことができない場合は、ネットワークフロー(F)最小費用流問題を(結局、私の言語が......吸います)
ここでは、コードは次のようになります。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
int cnt=1,n,m,s,t,mcost,mflow;
int head[5005],pre[5005];
int dis[5005];//dis为从S到这个点的最小总费用
int flow[5005];//flow为从S到这个点的最大可更新流量
bool vis[5005];//vis为这个点有没有被标记过(废话)
struct Edge
{
int to,nst,dis,flow;//dis是费用,flow是流量
}edge[100005];
void add(int a,int b,int c,int d)
{
edge[++cnt].nst=head[a];
edge[cnt].to=b;
edge[cnt].flow=c;//输入的时候仔细看一看
edge[cnt].dis=d;//要是把费用和流量搞反了就凉了
head[a]=cnt;
}
bool spfa()
{
queue <int> q;
memset(vis,0,sizeof(vis));
memset(dis,inf,sizeof(dis));
dis[s]=0;
vis[s]=1;
flow[s]=inf;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].nst)
{
int v=edge[i].to;
if(edge[i].flow&&dis[u]+edge[i].dis<dis[v])//如果边还有流量就尝试更新
{
dis[v]=edge[i].dis+dis[u];//更新最短路径
flow[v]=min(flow[u],edge[i].flow);//到达v点的水量取决于边剩余的容量和u点的水量
pre[v]=i;//记录路径
if(!vis[v])
{
vis[v]=1;
q.push(v);
}
}
}
}
return dis[t]!=inf;//如果还能跑到终点,就说明还不是最大流,还要继续跑spfa
}
void update()
{
int x=t;
while(x!=s)
{
int i=pre[x];
edge[i].flow-=flow[t];//正向边加上流量
edge[i^1].flow+=flow[t];//反向边减去流量
x=edge[i^1].to;//沿着记录下的路径寻找增广路
}
mflow+=flow[t];//累计流量
mcost+=flow[t]*dis[t];//累计费用
}
void EK(int s,int t)
{
while(spfa())//当还有多余流量时
update();
}
int main()
{
scanf("%d%d%d%d",&n,&m,&s,&t);
int a,b,c,d;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&a,&b,&c,&d);
add(a,b,c,d);
add(b,a,0,-d);//依旧不要忘记反向建边
}
EK(s,t);
printf("%d %d\n",mflow,mcost);
return 0;
}