差分約束學習筆記

2023-05-07 06:00:38

2023.5.6 寫的太爛了重新寫

差分約束系統

定義

差分約束系統是一種特殊的 \(n\) 元一次不等式組,它包含 \(n\) 個變數 \(x_{1},x_{2},...,x_{n}\) 以及 \(m\) 個約束條件,每一個約束條件都是兩個其中的變數做差構成的,形如 \(x_{i}-x_{j}\le c_{k}\),其中 \(1\le i,j\le n,i\ne j,1\le k\le m\) 並且 \(c_{k}\) 是常數(可以為正數或非正數)。
------- OI Wiki

通俗一點講,這類問題都是給定 \(n\) 個變數,\(m\) 個限制,類似於:

\[\left\{\begin{matrix} op_{1}:x_{1}-x_{2}=c_{1}\\ op_{2}:x_{4}-x_{n}=c_{2}\\ ......\\ op_{m}:x_{n}-x_{3}=c_{m} \end{matrix}\right. \]

有了這些條件,一般的題目會讓你求出一組合法的解,也就是求這 \(n\) 個變數的合法的值。

過程

我們可以建一個超級源點,然後向每一個點連一條邊權為 \(0\) 的邊,然後跑單源最短路;而上面的 \(m\) 個限制都可以變形為 \(x_{i}\le x_{j}+c_{k}\),這個東西很容易想到我們在跑最短路的時候的鬆弛操作裡的 \(dis[v]\le dis[u]+w\),因此我們就可以把每一個變數看作是一個圖中的點,對於每一個條件 \(x_{i}-x_{j}\le c_{k}\),從 \(j\)\(i\) 連一條邊權為 \(c_{k}\) 的有向邊。

我們在求解的時候一般用 SPFA 來跑,雖然他最壞的時間複雜度是 \(O(nm)\) 的,但是我們的差分約束裡面要是有負環的話,就說明是無解,再加上有負邊權,SPFA 這個已死的演演算法成了最好的方法,更何況他在一些隨機的圖中跑的飛快。

最後一個問題,最後轉化的式子是 \(x_{i}\le x_{j}+c_{k}\),為什麼跑最短路?

但是我覺得,當你建圖的時候使用的是 \(x_{i}-x_{j}\le c_{k}\) 形式的方程組建圖時,即 \(j\)\(i\) 連一條權值為 \(c_{k}\) 的邊,應該選擇跑最短路。

如果使用的是 \(x_{i}-s_{j}\ge c_{k}\) 形式的方程組來建圖時,應該選擇跑最長路。

P5960 【模板】差分約束演演算法

code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 50100
using namespace std;
int n,m,cnt,head[N];
queue<int>q;
struct SB{int w,v,next;}e[N<<1];
int dis[N],tot[N],vis[N];
inline void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
int SPFA()
{
	
	q.push(0);
	vis[0]=1;
	tot[0]++;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].w;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push(v);
				vis[v]=1;
				tot[v]++;
				if(tot[v]==n+1)
				return 0;
			}
		}
	}
	return 1;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	  dis[i]=INF;
	for(int i=1;i<=n;i++)
	  add(0,i,0);
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		cin>>x>>y>>z;
		add(y,x,z);
	}
	if(!SPFA())
	  cout<<"NO"<<endl;
	else
	  for(int i=1;i<=n;i++)
		cout<<dis[i]<<" ";
	return 0;
}