差分約束系統
文章目錄
概念
差分約束系統,指給定形如 x1 - x2 ≤ y1 或 x1 - 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] ,與求單源最短路演算法中的鬆弛操作極為相似
- 因為圖中大概率會出現負權邊,所以我們通常需要使用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)
此處省略多種毒瘤~~
。。。。。。
思路
一般輸出答案分為兩種:
- 輸出一組滿足的解
- 無滿足解
如何判斷是否有滿足解
即是判斷圖中是否存在負環
常見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;
}