1. 程式人生 > 其它 >差分約束系統

差分約束系統

技術標籤:模板差分法圖論

傳送門

文章目錄

概念

差分約束系統,指給定形如 x1 - x2 ≤ y1x1 - x2 >= y1 的若干個不等式,試求xn - xm的最大值或最小值。

這個不等式組有兩種情況:

  • 無解。
  • 有無陣列解。

需要使用的演算法:Spfa

  • 每一個約束條件的不等式 xi-xj≤bk
  • 轉換一下 xi ≤ bk+xj
  • 我們令bk= w(j, i),再將不等式中的i和j變數替換掉,i = v, j = u,將x陣列的名字改成d則不等式變
  • 為:d[v] ≤ w(u, v)+ d[u] ,與求單源最短路演算法中的鬆弛操作極為相似
    ,則鬆馳完成後d[v] 的狀態必定是≤ w(u, v)+ d[u]
  • 因為圖中大概率會出現負權邊,所以我們通常需要使用SPFA演算法

因為建出的圖可能不連通,所以我們需要虛擬一個不存在的值x0,並對於每一個點xn,我們都要多建一條有向邊xn - x0 <= 0(add(0,i,0)),這樣不會破壞任何不等式,且使原圖連通。我們通常稱之為超級源點

建圖

差分約束的難點在於建圖,而建圖的難點在於找出題目中隱藏的不等式。能使用差分約束解決的題目,一定在題面中體現了若干個形式相同或不同的不等式。

建圖的兩種方法

首先根據題目的要求進行不等式組的標準化。

%注意建圖函式add()中引數不同的位置

[ 1 ]

最小值

要求最小值,即求最長路,將不等式全部化成Xi – Xj >=k的形式,這樣建立j->i的邊,權值為k的邊。
1)Xi– Xj > k,
因為一般題目都是對整形變數的約束, 化為Xi – Xj >= k+1即可
2)Xi – Xj = k,
可以變為如下兩個: Xi – Xj >= k, Xi– Xj<= k,
後者進一步變為Xj – Xi >= -k,建立兩條邊即可。
3)a<=Xi – Xj <=b,
那麼可以轉為 Xi – Xj >=a和Xj– Xi>=-b。

	dis[i]設為-inf
 	if(dis[e[i]
.to]<dis[tmp]+e[i].w) { dis[e[i].to]=dis[tmp]+e[i].w; } -------- a-b<=k add(a,b,k);

[ 2 ] 最大值
要求最大值,即求最短路,將不等式全部化成xi – xj <= k的形式, 這樣建立j->i的邊,權值為k的邊.

	dis[i]設為0x3f
 	if(dis[e[i].to]>dis[tmp]+e[i].w)
	{
		dis[e[i].to]=dis[tmp]+e[i].w;
	}
	----------
	a-b<=k
	add(b,a,k);

a=b 建雙向邊 ——> add(a,b,0) add(b,a,0)
a>b ——> a-b>0 ——> b-a<=1 ——> add(a,b,1)
a<b ——> a-b<=-1 ——> add(b,a,-1)
a>=b+k ——> b-a<=k ——> add(a,b,k)
a<=b+k ——> a-b<=k ——> add(b,a,k)
此處省略多種毒瘤~~
。。。。。。

思路

一般輸出答案分為兩種:

  1. 輸出一組滿足的解
  2. 無滿足解

如何判斷是否有滿足解

即是判斷圖中是否存在負環

常見spfa判斷負環

不嚴謹證明:對於某個點x,與之相連的邊至少有n(總點數)-1種,
所以我們可以定義一個fh陣列記錄每次點x入隊的次數,
如果次數>n-1,即圖中存在負環。

inline void Spfa(int x)
{
	init();
	queue<int> q;
	dis[x]=0;vis[x]=1;fh[x]++;
	q.push(x);
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop();
		vis[tmp]=0;
		for(register int i=head[tmp];i;i=e[i].next)
		{
			if(dis[e[i].to]>dis[tmp]+e[i].w)
			{
				dis[e[i].to]=dis[tmp]+e[i].w;
				if(!vis[e[i].to]){
					q.push(e[i].to);
					vis[e[i].to]=1;
					fh[e[i].to]++;      //---
					if(fh[e[i].to]>n){  //---
						flag=1;         //---
						return;         //---
					}                   //---
				}
			}
		}
	}
}

判斷負環優化

上述的判斷負環方法最壞情況為O(N^2),在比賽中極大機率會TLE
所以接下來給大家推薦幾種優化方法

[ 1 ] SLF(Small Label First)優化

優化思路:將原佇列改成雙端佇列,對要加入佇列的點 p,如果 dist[p] 小於隊頭元素 u 的 dist[u],將其插入到隊頭,否則插入到隊尾。

inline void Spfa(int x)
{
	deque<int> q;
	dis[x]=0;vis[x]=1;
	q.push_back(x);
	while(!q.empty())
	{
		int tmp=q.front();
		q.pop_front();
		vis[tmp]=0;
		for(register int i=head[tmp];i;i=e[i].next)
		{
			if(dis[e[i].to]>dis[tmp]+e[i].w)
			{
				dis[e[i].to]=dis[tmp]+e[i].w;
				if(!vis[e[i].to]){
					if(!q.empty()&&dis[e[i].to]>dis[tmp])   //---
						q.push_back(e[i].to);               //---
					else                                    //---
						q.push_front(e[i].to);              //---
					vis[e[i].to]=1;
					fh[e[i].to]++;
					if(fh[e[i].to] > n)
					{
						cout << "No";
						exit(0);
					}
				}
			}
		}
	}
}

[ 2 ] DFS(深搜)優化
便於尋找圖中的負環,不建議用,會莫名其妙TLE!~~
不過這真的是神器!!!

對於20組資料
SLF優化 —— 4s
DFS優化 —— 172ms

在這裡插入圖片描述

神器啊!!!~!哈!!

bool spfa(int x)
{
	vis[x] = true;
	for(int i = 0; i < g[x].size(); i ++)
	{
		int to = g[x][i].to;
		int w = g[x][i].w;
		if(dis[x] + w < dis[to]){
			dis[to] = dis[x] + w;
			if(vis[to])return false;
			if(!spfa(to))return false;
		}
	}
	vis[x] = false;
	return true;
}

洛谷P5960題解

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+99;
int n,m,head[maxn],dis[maxn],vis[maxn],tot,fh[maxn],flag;
inline int Read()
{
    int x=0,f=1;char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') {x=x*10+c-'0'; c=getchar();}
    return x*f;
}
struct Edge{
	int to,next,w;
}e[maxn];
inline void init(){
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(fh,0,sizeof(fh));
}
inline void add(int a,int b,int w){
	e[++tot].next=head[a];
	e[tot].to=b;
	e[tot].w=w;
	head[a]=tot;	
}
void Spfa(){
	init();
	queue<int> q;
	dis[0]=0;vis[0]=1;
	q.push(0);
	while(!q.empty()){
		int tmp=q.front();
		q.pop();
		vis[tmp]=0;
		for(int i=head[tmp];i;i=e[i].next){
			if(dis[e[i].to]>dis[tmp]+e[i].w){
				dis[e[i].to]=dis[tmp]+e[i].w;
				if(!vis[e[i].to]){
					q.push(e[i].to);
					vis[e[i].to]=1;
					fh[e[i].to]++;
					if(fh[e[i].to]>n){
						flag=1;
						return;
					}
				}
			}
		}
	}
}
int main(){
	cin>>n>>m;
	//init();
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		add(v,u,w);
	}
	for(int i=1;i<=n;i++)add(0,i,0);
	Spfa();
	if(!flag){
		for(int i=1;i<=n;i++)cout<<dis[i]<<" ";
	}
	else cout<<"NO";
	return 0;
}