1. 程式人生 > 實用技巧 >[模板][最短路] 差分約束系統

[模板][最短路] 差分約束系統

差分約束系統

定義 來自某度百科

如果一個系統由 \(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$ 的邊,

然後開始轉化:

  1. 轉化為最

    \[ \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 $ 的最解,

  2. 轉化為最

    \[ \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;
}