差分約束系統

【模板】差分約束

洛谷P5960 【模板】差分約束算法html

前置知識

想要作對差分約束,負環斷定這個知識確定是要會的,不會的能夠看個人另外一篇博客qwqnode

另外,若干您不想看題解,也能夠直接看判斷負環的模板題P3385c++


差分約束系統

(如下內容部分摘自《算法競賽進階指南》)算法

  • 差分約束系統

差分約束系統是一種特殊的\(N\)元一次不等式函數

它包含\(N\)個變量\(X_1\) ~ \(X_n\)以及\(M\)各約束條件,每一個約束條件都是由兩個變量作差構成的(因此是差分嘛!),形如\(X_i-X_n≤C_k\),其中\(C_k\)是常數(能夠是負數也能夠是非負數),\(1≤i,j≤N,1≤k≤M\)spa

咱們要解決的問題就是:求一組解\(X_1=a_1,X_2=a_2···X_n=a_n\),使全部約束條件都獲得知足.net

  • 轉換思想

差分約束系統的每一個約束條件\(X_i-X_n≤C_k\)能夠變形爲\(X_i≤X_j+C_k\)code

有沒有以爲有那麼一點點的熟悉?htm

嗯...和求解單源最短路中的三角形不等式\(dis[i]≤dis[j]+e[i].val\)\(dis[i]-dis[j]≤e[i].val\))很是類似blog

所以能夠三角形不等式推廣:把每一個變量\(X_i\)看做有向圖中的一個節點\(i\),對於每一個約束條件\(X_i-X_n≤C_k\),從節點\(j\)向節點\(i\)連一條長度爲\(C_k\)的有向邊

如今來看下面給出的這張圖,來說解一下差分約束中的最短路和最長路(可能有點繞,可是圖很好理解):

從這張圖中的例子,咱們不可貴出(重點啊):

  1. 差分約束跑最短路,跑出的結果是全部解中的最大解

  2. 差分約束跑最長路,跑出的結果是全部解中的最小解

可是,最短路和最長路也是能夠互相轉換的,什麼意思?(須要掌握)

在某些題目中,約束條件形如\(x_i-X-j≥C_k\),咱們有兩種方式解決:

  1. 能夠從\(j\)\(i\)連一條長度爲\(C_k\)的有向邊,而後計算單源最長路,若圖中有正環則無解

  2. 咱們也能夠把約束條件轉化成\(X_j-X_i≤-C_k\),再按單源最短路進行計算

  • 解題模型

PS:差分約束是有多組解的,可是題目通常只會要求輸出其中任意一種

  1. 創建「超級源點0」,將\(0\)與每一個點\(i\)連一條長爲\(0\)的邊,而後以\(0\)爲起點求單源最短路

  2. 不創建「超級源點」,將每個點都入隊而後去跑最短路

若圖中存在負環,則給定的差分約束系統無解;不然\(X_i=dis[i]\)就是差分約束系統的一組解


例題代碼

如今給出這道模板題的代碼(以下是\(SPFA\)版本的,下面會給出\(Ford\)版本的函數段):

#include <bits/stdc++.h>
using namespace std;
queue<int> q;
int n,m,u,v,w,tot;
int dis[200010],vis[200010],cnt[200010],head[200010];

struct node {
	int to,net,val;
} e[200010];

inline void add(int u,int v,int w) {
	e[++tot].to=v;
	e[tot].val=w;
	e[tot].net=head[u];
	head[u]=tot;
}

inline bool spfa() {
	for(register int i=0;i<=n;i++) {
		vis[i]=0;
		dis[i]=20050206;
	}
	dis[0]=0;
	vis[0]=1;
	q.push(0);
	while(!q.empty()) {
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(register int i=head[x];i;i=e[i].net) {
			int v=e[i].to;
			if(dis[v]>dis[x]+e[i].val) {
				dis[v]=dis[x]+e[i].val;
				if(cnt[v]>=n) return false;
				if(!vis[v]) {
					vis[v]=1;
					cnt[v]++;
					q.push(v);
				}
			}
		} 
	}
	return true;
}

int main() {
	scanf("%d%d",&n,&m);
	for(register int i=1;i<=m;i++) {
		scanf("%d%d%d",&u,&v,&w);
		add(v,u,w);
	}
	for(register int i=1;i<=n;i++) add(0,i,0);
	if(spfa()==false) puts("NO");
	else {
		for(register int i=1;i<=n;i++) printf("%d ",dis[i]);
	}
	return 0;
}

下面是\(Ford\)版本的函數段,其餘的和上面的沒什麼區別

inline bool ford() {
	for(register int i=0;i<=n;i++) dis[i]=20050206;
	dis[0]=0;
	for(register int i=0;i<n;i++) {
		for(register int j=1;j<=tot;j++) {
			if(dis[e[j].fro]+e[j].val<dis[e[j].to]) {
				dis[e[j].to]=dis[e[j].fro]+e[j].val;
			}
		}
	}
	for(register int i=1;i<=tot;i++) {
		if(dis[e[i].fro]+e[i].val<dis[e[i].to]) return false;
	}
	return true;
}

好題推薦


最後,關於上面其餘好題的題解我會陸陸續續更新在個人博客中,歡迎你們來踩qwq

若是有任何不懂或是個人題解有誤的,歡迎你們在評論區留言,我會及時回覆、改正,謝謝你們啊orz

相關文章
相關標籤/搜索