1. 程式人生 > 實用技巧 >[筆記]差分約束系統

[筆記]差分約束系統

[筆記]差分約束系統

演算法用途

​ 當題面給出許多形如\(x_i - x_j ≤ c_k(c為常數)\)的不等式時,並讓你求出一組滿足條件的解時,就可以運用差分約束系統.

演算法描述

​ 首先,我們對不等式進行變形,變成\(x_i ≤ x_j + c_k\)的形式,這時我們可以發現,這和求最短路時的式子(\(dis[v] ≥ dis[u] + w_{u→v})\))很像,但我們發現這兩個式子的不等號方向相反,但這其實並不影響我們用最短路的方法來解決問題。

​ 首先我們假設有一個不等式組

\[\begin{cases} t_i ≤ t_{j'} + b\\ t_i ≤ t_{j''} + b\\ t_i ≤ t_{j'''} + b \end{cases} \]

那麼要使\(t_i\)滿足所有不等式並且取到最大值,則答案應該是\(min(t_{j'},t_{j''},t_{j'''}) + b\),因為如果要滿足所有不等式則要取最小值(同小取小),實際情況\(t_j\)集合的元素可能不止\(3\)個,我們直接用\(t_j\)來表示所有\(t_j\)集合中的元素,則答案可以表示為\(t_i = min(t_j + b)\),這和\(SPFA\)中的式子(\(dis_{ti} = min(dis_{tj} + w_{i→j})\)一樣,因此我們證明了差分約束的問題可以用最短路來解決。具體方法是連一條從\(j\)\(i\)的權值為\(b\)的有向邊,再跑\(SPFA\)

即可

​ 通過上面的推理我們知道當我們跑最短路時,可以求出滿足條件的最大解,那麼我們可以同理推出當我們用\(SPFA\)跑最長路時可以求出滿足條件的最小值,那麼究竟什麼時候跑最短路,什麼時候跑最長路呢?

例題講解

​ 下面我們來看兩道題目。T1,T2.

​ 在第一題中,題目的要求與上文我們舉的例子相符合,因此是跑最短路。

​ 再來看第二題,題目要求的是讓我們在所有區間\(a_i\)\(b_i\)中均選出不少於\(c_i\)個數來滿足要求。我們假設\(t_i\)表示的是在前\(i\)個數中選了\(t_i\)個,那麼我們可以得到如下不等式組:

\[\begin{cases} t_j - t_i ≥ c_{i,j}\\ t_{j'} - t_{i'} ≥ c_{i',j'}\\ t_{j''} - t_{i''} ≥ c_{i'',j''}\\ \end{cases} \]

類比上文的建圖方法,我們以同樣的方式建邊,但要注意的是區間\(i→j\)所有選的數的個數應該表示為\(t_j - t_{i - 1}\)因為第\(i\)位的數字也可能被選中。但是這一個條件並不全, 我們知道一定會有\(0≤t_i - t_{i+1} ≤ 1\),因為可能存在第\(i+1\)位選或不選的情況,但由於一個建邊的條件並不能滿足兩個不等式(上式可以拆分為兩個不等式),所以我們通過兩個條件來約束.根據拆分完的不等式來進行約束\(t_{i+1}-t_i ≥ 0\)\(t_{i+1} ≥ t_i + 0\)因此從\(i\)連一條到\(j\)的權值為\(0\)的有向邊;第二個不等式是\(t_i - t_{i+1} ≤ 1\)\(t_i ≤ t_{i+1} + 1\),因此從\(i+1\)連一條到\(i\)的權值為\(0\)的有向邊,這樣就沒有漏過任何一個約束條件了。

程式碼

T1

#include <bits/stdc++.h>
using namespace std;
struct node{int to,next,w;}e[50010];
int fir[50010],tot = 0,times[50010],n,m;
void add(int x,int y,int z){
	tot++;
	e[tot].w = z;
	e[tot].to = y;
	e[tot].next = fir[x];
	fir[x] = tot;
	return;
}
bool in[50010];
int dis[50010];
bool spfa(int x){
	queue < int > q;
	for(int i = 1;i <= n;i++)dis[i] = 1e9;
	while(!q.empty())
		q.pop();
	memset(in,false,sizeof(in));
	memset(times,0,sizeof(times));
	in[x] = true;
	times[x]++;
	dis[x] = 0;
	q.push(x);
	while(!q.empty()){
		int k = q.front();
		q.pop();in[k] = false;
		for(int i = fir[k];i;i = e[i].next){
			if(dis[e[i].to] > dis[k] + e[i].w){
				dis[e[i].to] = dis[k] + e[i].w;
				if(!in[e[i].to]){
					q.push(e[i].to);
					times[e[i].to]++;
					in[e[i].to] = true;
					if(times[e[i].to] > n)
						return false;
				}
			}
		}
	}
	return true;
}
int main(){
	cin>>n>>m;
	for(int i = 1;i <= m;i++){
		int x,y,z;
		cin>>x>>y>>z;
		add(y,x,z);
	}
	for(int i = 1;i <= n;i++){
		add(0,i,0);
	}
	if(!spfa(0)){
		cout<<"NO"<<endl;
		return 0;
	}
	for(int i = 1;i <= n;i++){
		cout<<dis[i]<<" ";
	}
	cout<<endl;
	return 0;
}

T2

#include <bits/stdc++.h> 
using namespace std;
struct node{
	int to,next,w;
}edge[150010];
const int inf = INT_MAX;
long long fir[150010],tim[150010],dis[150010],tot,n,maxx,minn;
bool in[150010];
void add(int x,int y,int z){
	tot++;
	edge[tot].to = y;
	edge[tot].next = fir[x];
	edge[tot].w = z;
	fir[x] = tot;
}
void spfa(){
	queue < int > q;
	while(!q.empty())q.pop();
	memset(in,false,sizeof(in)); 
	memset(tim,0,sizeof(tim));
	memset(dis,-63,sizeof(dis));
	dis[minn] = 0;in[minn] = true;tim[minn] = 1;q.push(minn);
	while(!q.empty()){
		int x = q.front();q.pop();
		in[x] = false;
		for(int i = fir[x];i != -1;i = edge[i].next){
			if(dis[edge[i].to] < dis[x] + edge[i].w){
				dis[edge[i].to] = dis[x] + edge[i].w;
				if(!in[edge[i].to]){
					tim[edge[i].to]++;
					if(tim[edge[i].to] > maxx)return;
					in[edge[i].to] = true;
					q.push(edge[i].to);
				}
			}
		}
	}
	return;
}
int main(){
		scanf("%d",&n);
		memset(fir,-1,sizeof(fir));
		memset(edge,0,sizeof(edge));
		maxx = -1;minn = inf;
		tot = 0;
		for(int i = 1;i <= n;i++){
			long long x,y,z;
			scanf("%lld%lld%lld",&x,&y,&z);
			if(x > y)swap(x,y);
			add(x - 1,y,z);
			maxx = max(maxx,y);
			minn = min(minn,x - 1);
		}
		for(int i = minn;i <= maxx;i++){
			add(i,i + 1,0);
			add(i + 1,i,-1);
		}
		spfa();
		printf("%d\n",dis[maxx]);
	return 0;
}

完結撒花