[演算法學習筆記] 差分約束

SXqwq發表於2024-03-16

Description

一個差分約束系統是這樣的。

給定一組包含 \(m\) 個不等式,有 \(n\) 個不等式形如:

\[\begin{cases} x_{c_1}-x_{c'_1}\leq y_1 \\x_{c_2}-x_{c'_2} \leq y_2 \\ \cdots\\ x_{c_m} - x_{c'_m}\leq y_m\end{cases} \]

求任意一組可行解。

Solution

觀察這個式子:

\(x_{c1}-x_{c'1} \le y_1\)

我們將這個式子移項,得:

\(x_{c1}\le y_1+x_{c'1}\)

觀察到這個式子類似於求最短路中的鬆弛操作。我們可以將 \(x_{c'1}\)\(x_{c1}\) 連一條長度為 \(y_1\) 的邊。然後跑最短路即可。

至於從哪個點開始跑呢?實際上我們從任意一個點開始跑都可以。但是很多情況我們得到的圖都是不連通的。因此我們習慣上人為新增一個超級源點。

也就是從 \(0\) 號節點向每一個點連一條長度為 \(0\) 的邊。也就是人為新增了以下約束條件:

\[\begin{cases} x_{c_1}-x_{0}\leq 0 \\x_{c_2}-x_{0} \leq 0 \\ \cdots\\ x_{c_m} - x_{0}\leq 0\end{cases} \]

注意到此時所有點的最短路都 \(\le 0\)。如果我們想要非負解呢?

這也很簡單,根據不等式的基本性質,將所有滿足約束的 \(x_i\) 同加上一個值,仍然滿足約束。我們只需要在超級源點連邊的時候賦為 \(w\) 即可。

這樣我們得到的就是 \(\forall x_i \le w\) 的一組解。事實上,可以證明這是滿足 \(\forall x_i \le w\) 的最大解。

那麼如何求最小解呢?只需要求最長路即可。

對於判定無解,我們只需要在求最短路的時候判負環即可。如果出現負環,則最短路無解,顯然不等式組無解。

題目中有時給出的不等式組並非差分約束系統一般形式,我們可以透過一系列轉換。靈活應用轉換為樸素查分約束系統。

模板程式碼

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PAIR;
const int N = 100010;
const int INF = 0x3f3f3f3f;
int vis[N];
int dist[N];
int cnt[N];
vector <PAIR> Edge[N];
int n,m;
bool spfa()
{
	queue <int> q;
	q.push(0);
	vis[0] = 1;
	dist[0] = 0;
	while(!q.empty())
	{
		int u = q.front();
	//	cout<<cnt[u]<<endl;
		if(cnt[u] >= n)
		{
			return false;
		}
		q.pop();
		vis[u] = 0;
		for(auto v:Edge[u])
		{
			int vv = v.first,ww = v.second;
			if(dist[vv] > dist[u] + ww)
			{
				cnt[vv] ++;
				//cout<<cnt[u]<<endl;
				dist[vv] = dist[u] + ww;
				if(!vis[vv]) q.push(vv);
			}
		}
	}
	return true;
}
int main()
{
//	freopen("input.txt","r",stdin);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int v,u,w;
		cin>>v>>u>>w;
		Edge[u].push_back({v,w});	
	} 
	for(int i=1;i<=n;i++) Edge[0].push_back({i,0});
	for(int i=1;i<=n;i++) dist[i] = INF;
	if(!spfa()) 
	{
		cout<<"NO"<<endl;
		return 0;
	}
	for(int i=1;i<=n;i++) cout<<dist[i]<<" ";
	cout<<endl;
	return 0;
}

相關文章