1. 程式人生 > 實用技巧 >差分約束

差分約束

前言

沒啥好說的,存個板子,感覺這東西這輩子都不會考到

講解

保留環節:百度百科自學時間

差分約束系統是用於求包含n個未知數,m個不等式,形如:

\[\begin{cases} x_{c_1}-x_{c_1'}\le y_1\\ x_{c_2}-x_{c_2'}\le y_2\\ ...\\ x_{c_m}-x_{c_m'}\le y_m \end{cases}\]

的方程組的解的一種演算法

試圖大喘氣

我們提出一個式子來觀察:

\(x_{i}-x_{j}\le y\)

將其轉換一下:

\(x_i\le y+x_j\)

誒!這是不是跟最短路的更新有點像,這可以轉換成什麼?

\(j\)\(i\)

的最短路要小於等於\(y\)!(感嘆號,不是階乘...)

這相當於什麼?直接對於\(j\)\(i\)連一條權值為\(y\)的邊

然後跑最短路即可

什麼,有可能出現負環?

我們思考一下負環意味著什麼

\(x_i\le y+x_i(y<0)\)

這不是顯然無解?

當然,我們根據這種方法構造出來的圖有可能不是連通的,只需要構建一個超級源點\(0\),對其向\(1\)\(n\)每個點連一條權值為\(0\)的邊即可

當然由於存在負邊權,我們不能使用Dijkstra,只能使用SPFA

因為我不會Bellman-Ford

拓展

\(x_i-x_j\ge y\)的形式可以兩邊同乘\(-1\)改變符號

\(x_i-x_j=y\)

的形式可以轉換為:\(x_i-x_j\le y\)\(x_i-x_j\ge y\)兩個條件

練習

板題(洛谷)

程式碼

板題程式碼

int head[MAXN],tot;
struct edge
{
	int v,w,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y,int z)
{
	e[++tot].v = y;
	e[tot].w = z;
	e[tot].nxt = head[x];
	head[x] = tot;
}

int dis[MAXN],cnt[MAXN];
bool vis[MAXN];
void spfa()
{
	queue<int> q;
	q.push(0); vis[0] = 1;
	for(int i = 1;i <= n;++ i) dis[i] = INF;
	while(!q.empty())//正常SPFA
	{
		int t = q.front(); q.pop();
		cnt[t]++;
		vis[t] = 0;
		if(cnt[t] > n) {printf("NO");return;}//存在負環
		for(int i = head[t]; i ;i = e[i].nxt)
			if(dis[e[i].v] > dis[t] + e[i].w)
			{
				dis[e[i].v] = dis[t] + e[i].w;
				if(!vis[e[i].v])
					q.push(e[i].v),vis[e[i].v] = 1;
			}
	}
	for(int i = 1;i <= n;++ i) Put(dis[i],' ');
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	n = Read(); m = Read();
	for(int i = 1;i <= m;++ i)
	{
		int u = Read(),v = Read();
		Add_Edge(v,u,Read());//注意連邊順序與權值
	}
	for(int i = 1;i <= n;++ i) Add_Edge(0,i,0);//建立超級源點與邊
	spfa();
	return 0;
}