想要作對差分約束,負環斷定這個知識確定是要會的,不會的能夠看個人另外一篇博客qwqnode
另外,若干您不想看題解,也能夠直接看判斷負環的模板題P3385c++
(如下內容部分摘自《算法競賽進階指南》)算法
差分約束系統是一種特殊的\(N\)元一次不等式函數
它包含\(N\)個變量\(X_1\) ~ \(X_n\)以及\(M\)各約束條件,每一個約束條件都是由兩個變量作差構成的(因此是差分嘛!),形如\(X_i-X_n≤C_k\),其中\(C_k\)是常數(能夠是負數也能夠是非負數),\(1≤i,j≤N,1≤k≤M\)spa
咱們要解決的問題就是:求一組解\(X_1=a_1,X_2=a_2···X_n=a_n\),使全部約束條件都獲得知足.net
差分約束系統的每一個約束條件\(X_i-X_n≤C_k\)能夠變形爲\(X_i≤X_j+C_k\)code
有沒有以爲有那麼一點點的熟悉?htm
嗯...和求解單源最短路中的三角形不等式\(dis[i]≤dis[j]+e[i].val\)(\(dis[i]-dis[j]≤e[i].val\))很是類似blog
所以能夠三角形不等式推廣:把每一個變量\(X_i\)看做有向圖中的一個節點\(i\),對於每一個約束條件\(X_i-X_n≤C_k\),從節點\(j\)向節點\(i\)連一條長度爲\(C_k\)的有向邊
如今來看下面給出的這張圖,來說解一下差分約束中的最短路和最長路(可能有點繞,可是圖很好理解):
從這張圖中的例子,咱們不可貴出(重點啊):
差分約束跑最短路,跑出的結果是全部解中的最大解
差分約束跑最長路,跑出的結果是全部解中的最小解
可是,最短路和最長路也是能夠互相轉換的,什麼意思?(須要掌握)
在某些題目中,約束條件形如\(x_i-X-j≥C_k\),咱們有兩種方式解決:
能夠從\(j\)到\(i\)連一條長度爲\(C_k\)的有向邊,而後計算單源最長路,若圖中有正環則無解
咱們也能夠把約束條件轉化成\(X_j-X_i≤-C_k\),再按單源最短路進行計算
PS:差分約束是有多組解的,可是題目通常只會要求輸出其中任意一種
創建「超級源點0」,將\(0\)與每一個點\(i\)連一條長爲\(0\)的邊,而後以\(0\)爲起點求單源最短路
不創建「超級源點」,將每個點都入隊而後去跑最短路
若圖中存在負環,則給定的差分約束系統無解;不然\(X_i=dis[i]\)就是差分約束系統的一組解
如今給出這道模板題的代碼(以下是\(SPFA\)版本的,下面會給出\(Ford\)版本的函數段):
#include <bits/stdc++.h> using namespace std; queue<int> q; int n,m,u,v,w,tot; int dis[200010],vis[200010],cnt[200010],head[200010]; struct node { int to,net,val; } e[200010]; inline void add(int u,int v,int w) { e[++tot].to=v; e[tot].val=w; e[tot].net=head[u]; head[u]=tot; } inline bool spfa() { for(register int i=0;i<=n;i++) { vis[i]=0; dis[i]=20050206; } dis[0]=0; vis[0]=1; q.push(0); while(!q.empty()) { int x=q.front(); q.pop(); vis[x]=0; for(register int i=head[x];i;i=e[i].net) { int v=e[i].to; if(dis[v]>dis[x]+e[i].val) { dis[v]=dis[x]+e[i].val; if(cnt[v]>=n) return false; if(!vis[v]) { vis[v]=1; cnt[v]++; q.push(v); } } } } return true; } int main() { scanf("%d%d",&n,&m); for(register int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); add(v,u,w); } for(register int i=1;i<=n;i++) add(0,i,0); if(spfa()==false) puts("NO"); else { for(register int i=1;i<=n;i++) printf("%d ",dis[i]); } return 0; }
下面是\(Ford\)版本的函數段,其餘的和上面的沒什麼區別
inline bool ford() { for(register int i=0;i<=n;i++) dis[i]=20050206; dis[0]=0; for(register int i=0;i<n;i++) { for(register int j=1;j<=tot;j++) { if(dis[e[j].fro]+e[j].val<dis[e[j].to]) { dis[e[j].to]=dis[e[j].fro]+e[j].val; } } } for(register int i=1;i<=tot;i++) { if(dis[e[i].fro]+e[i].val<dis[e[i].to]) return false; } return true; }
洛谷P3275 糖果 (差分約束仍是算經典的一道題,不過也能夠縮點+拓撲A掉)
洛谷P2294 狡猾的商人 (思路巧妙的差分約束/並查集/貪心/DP(後兩種比較玄學))
最後,關於上面其餘好題的題解我會陸陸續續更新在個人博客中,歡迎你們來踩qwq
若是有任何不懂或是個人題解有誤的,歡迎你們在評論區留言,我會及時回覆、改正,謝謝你們啊orz