[模板][最短路] 差分約束系統
阿新 • • 發佈:2020-07-30
差分約束系統
定義 來自某度百科
如果一個系統由 \(n\) 個變數和 \(m\) 個約束條件組成,形成 \(m\) 個形如 \(a_i-a_j≤k\) 的不等式 (\(i,j\in[1,n]\), $k$為常數),則稱其為差分約束系統(system of difference constraints)。亦即,差分約束系統是求解關於一組變數的特殊不等式組的方法。
簡單來說,給你一組不等式:
\[ \left \{ \begin{array}{} x_1 - x_2 \le c_1 \\ x_2 - x_3 \le c_2 \\ \ \ \ \ \ \cdots \\ x_i - x_j \le c_k \end{array}\right. \]求這組不等式的可行解。
解法
該問題等價於在一個有向圖上求最短/長路,
首先建立一個超級源點, 向每個節點連結一條邊權為 $0$ 的邊,
然後開始轉化:
轉化為最短路
\[ \left \{ \begin{array}{} x_1 \le c_1 + x_2\\ x_2 \le c_2 + x_3\\ \ \ \ \ \ \cdots \\ x_i \le c_k + x_j \end{array}\right. \]這樣求出的解是 $x_i \le 0 $ 的最大解,
轉化為最長路
\[ \left \{ \begin{array}{} x_1 \ge -c_1 + x_2\\ x_2 \ge -c_2 + x_3\\ \ \ \ \ \ \cdots \\ x_i \ge -c_k + x_j \end{array}\right. \]這樣求出的解是 $x_i \ge 0 $ 的最小
我們要做的就是從大於號左邊(小於號右邊)向大於號右邊(小於號左邊)連線一條邊權為 \(c_i\)(\(-c_i\)) 的邊,跑最短路即可。
無解?
聯絡最短路,發現當圖上有負環無解,
所以跑 \(SPFA\) 即可。
模板
[Luogu P5960 模板 差分約束演算法](https://www.luogu.com.cn/problem/P5960)
// 註釋掉的是跑最長路找最小正數可行解的方法 # include <iostream> # include <cstdio> # include <queue> # include <cstring> # define MAXN 5005 # define MAXM 10005 using namespace std; struct edge{ int u, v, next, w; }e[MAXM]; int hd[MAXM], cntE; void AddE(int u, int v, int w){ e[++cntE].u = u, e[cntE].v = v, e[cntE].next = hd[u], hd[u] = cntE; e[cntE].w = w; } // int dis[MAXN], cntQ[MAXN]; bool inQ[MAXN]; bool SPFA(int from, int lim){ queue<int>Q; memset(inQ, 0, sizeof(inQ)); memset(dis, 0x3f, sizeof(dis)); // for(int i = 1; i <= lim-1; i++){ // dis[i] = -100000; // } dis[from] = 0; Q.push(from); inQ[from] = 1; while(!Q.empty()){ int now = Q.front(); Q.pop(); inQ[now] = 0; for(int i = hd[now]; i; i = e[i].next){ if(dis[e[i].v] > dis[now] + e[i].w){ // if(dis[e[i].v] < dis[now] + e[i].w){ dis[e[i].v] = dis[now] + e[i].w; if(!inQ[e[i].v]){ Q.push(e[i].v); inQ[e[i].v] = 1; cntQ[e[i].v] += 1; if(cntQ[e[i].v] == lim){ return false; } } } } } return true; } // int main(){ int n, m; cin>>n>>m; for(int i = 1, u, v, w; i <= m; i++){ cin>>u>>v>>w; AddE(v, u, w); // 注意這裡的連邊方向,應該是從大於連向小於 // AddE(u, v, -w); } for(int i = 1; i <= n; i++){ AddE(0, i, 0); } // 建立超級源點 bool chk = SPFA(0, n+1); if(chk){ for(int i = 1; i <= n; i++){ cout<<dis[i]<<' '; } } else{ cout<<"NO"; } return 0; }