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