差分約束系統+SPFA/Bellman判斷負權迴路+uva515
轉自:http://blog.csdn.net/pi9nc/article/details/12421417
差分約束系統:
如果一個系統由n個變數和m個約束條件組成,其中每個約束條件形如xj-xi<=bk(i,j∈[1,n],k∈[1,m]),則稱其為差分約束系統(system of difference constraints)。亦即,差分約束系統是求解關於一組變數的特殊不等式組的方法。
差分約束可以轉化話單源最短路求解。因為單源最短路徑滿足三角不等式d[v] <= d[u] + w(u, v), 這裡的 <= 可以為改 >= 只要改變一下初始化條件即可。
差分約束題目有兩種,一種求最大值,另外一種求最小值。
(1)當題目是求滿足給定不等式的最小值時,就是求圖的最長路。
建圖方式如下:a – b >= c,對應於邊b –> a w(b, a) = c, 然後求最短路;判斷條件是:d[v] <= d[u] + w(u, v), 初始化d[]為-INF. 這樣求出的d[]就是滿足條件的最小值。原因很簡單,因為d[i] 是從-INF開始增大,根據不等式逐漸增大,當滿足所有不等式時,那麼d[i]肯定是最小的了。
(2)當題目是求滿足給定不等式的最大值時,就是求圖的最短路。
建圖方式如下:a – b <= c,對應於邊b –> a w(b, a) = c, 然後求最長路;判斷條件是:d[v] >= d[u] + w(u, v), 初始化d[]為INF.這樣求出的d[]就是最大值。原因和上面一樣,因為是從INF逐漸減小的,當滿足完所有條件時,就停止。那麼d[i]是最大的了。
常見單源最短路徑方法有bellman_ford, spfa, Dijkstra
bellman_ford演算法,適用於邊值負權的圖,還能判斷是否存在負權迴路,它是把所有的邊都鬆弛n-1次。演算法複雜度為O(VE),這個方法效率不高,在資料規模大時,往往會TLM。
spfa演算法,是bellman_ford演算法的改進版。它通過一個佇列或棧,來儲存需要更新的頂點。它減去了bellman演算法很多不必要的鬆弛,因此它的效率比bellman要好,為O(ke)。
Dijkstra演算法,它只適用於邊值為非負的圖上。它基於貪心,每次選取最短的邊。可以用heap來優化,在正權圖也可以考慮。
體會:雖然bellman_ford不及spfa的效率,但它在判斷是否存在負權迴路比spfa方便。它能判斷不連通的圖,而spfa判斷不連通圖時有可能不準確,當開始的源點到達不了某些頂點時,那麼那些頂點就無法檢驗了。為此,spfa需要新增一個超源點來使得圖連通(還有一個方法:就是先把所有頂點入隊),而bellman不需要新增超源點, 因為它是對圖的每一條邊都進行n-1次鬆弛。有時候超源點不好新增,這時可以考慮用bellman。
還有一點,當適用spfa新增超源點使得圖連通(超源點S0 到其他頂點距離都為0)時,用最短路求出的解, 除了滿足給定的不等式外,還會滿足下列的不等式:
S1 – S0 <= 0
S2 – S0 <= 0
…
Sn – S0 <= 0
也就是說S1 ~ Sn是 < 0 的,這個對不同問題,要具體分析。這些額外滿足的不等式,不能與題目要求的矛盾。
轉自:http://hi.baidu.com/tx_li/item/1fd076dfb56971f83dc2cbfd
比如有這樣一組不等式:
X1 - X2 <= 0
X1 - X5 <= -1
X2 - X5 <= 1
X3 - X1 <= 5
X4 - X1 <= 4
X4 - X3 <= -1
X5 - X3 <= -3
X5 - X4 <= -3
不等式組(1)
全都是兩個未知數的差小於等於某個常數(大於等於也可以,因為左右乘以-1就可以化成小於等於)。這樣的不等式組就稱作差分約束系統。
這個不等式組要麼無解,要麼就有無陣列解。因為如果有一組解{X1, X2, ..., Xn}的話,那麼對於任何一個常數k,{X1 + k, X2 + k, ..., Xn + k}肯定也是一組解,因為任何兩個數同時加一個數之後,它們的差是不變的,那麼這個差分約束系統中的所有不等式都不會被破壞。
差分約束系統的解法利用到了單源最短路徑問題中的三角形不等式。即對於任何一條邊u -> v,都有:
d(v) <= d(u) + w(u, v)
其中d(u)和d(v)是從源點分別到點u和點v的最短路徑的權值,w(u, v)是邊u -> v的權值。
顯然以上不等式就是d(v) - d(u) <= w(u, v)。這個形式正好和差分約束系統中的不等式形式相同。於是我們就可以把一個差分約束系統轉化成一張圖,每個未知數Xi對應圖中的一個頂點Vi,把所有不等式都化成圖中的一條邊。對於不等式Xi - Xj <= c,把它化成三角形不等式:Xi <= Xj + c,就可以化成邊Vj -> Vi,權值為c。最後,我們在這張圖上求一次單源最短路徑,這些三角形不等式就會全部都滿足了,因為它是最短路徑問題的基本性質嘛。
話說回來,所謂單源最短路徑,當然要有一個源點,然後再求這個源點到其他所有點的最短路徑。那麼源點在哪呢?我們不妨自已造一個。以上面的不等式組為例,我們就再新加一個未知數X0。然後對原來的每個未知數都對X0隨便加一個不等式(這個不等式當然也要和其它不等式形式相同,即兩個未知數的差小於等於某個常數)。我們索性就全都寫成Xn - X0 <= 0,於是這個差分約束系統中就多出了下列不等式:
X1 - X0 <= 0
X2 - X0 <= 0
X3 - X0 <= 0
X4 - X0 <= 0
X5 - X0 <= 0
不等式組(2)
對於這5個不等式,也在圖中建出相應的邊。最後形成的圖如下:
圖1
圖中的每一條邊都代表差分約束系統中的一個不等式。現在以V0為源點,求單源最短路徑。最終得到的V0到Vn的最短路徑長度就是Xn的一個解啦。從圖1中可以看到,這組解是{-5, -3, 0, -1, -4}。當然把每個數都加上10也是一組解:{5, 7, 10, 9, 6}。但是這組解只滿足不等式組(1),也就是原先的差分約束系統;而不滿足不等式組(2),也就是我們後來加上去的那些不等式。當然這是無關緊要的,因為X0本來就是個局外人,是我們後來加上去的,滿不滿足與X0有關的不等式我們並不在乎。
也有可能出現無解的情況,也就是從源點到某一個頂點不存在最短路徑。也說是圖中存在負權的圈。這一點我就不展開了,請自已參看最短路徑問題的一些基本定理。
其實,對於圖1來說,它代表的一組解其實是{0, -5, -3, 0, -1, -4},也就是說X0的值也在這組解當中。但是X0的值是無可爭議的,既然是以它作為源點求的最短路徑,那麼源點到它的最短路徑長度當然是0了。因此,實際上我們解的這個差分約束系統無形中又存在一個條件:
X0 = 0
也就是說在不等式組(1)、(2)組成的差分約束系統的前提下,再把其中的一個未知數的值定死。這樣的情況在實際問題中是很常見的。比如一個問題表面上給出了一些不等式,但還隱藏著一些不等式,比如所有未知數都大於等於0或者都不能超過某個上限之類的。比如上面的不等式組(2)就規定了所有未知數都小於等於0。
對於這種有一個未知數定死的差分約束系統,還有一個有趣的性質,那就是通過最短路徑演算法求出來的一組解當中,所有未知數都達到最大值。下面我來粗略地證明一下,這個證明過程要結合Bellman-Ford演算法的過程來說明。
假設X0是定死的;X1到Xn在滿足所有約束的情況下可以取到的最大值分別為M1、M2、……、Mn(當然我們不知道它們的值是多少);解出的源點到每個點的最短路徑長度為D1、D2、……、Dn。
基本的Bellman-Ford演算法是一開始初始化D1到Dn都是無窮大。然後檢查所有的邊對應的三角形不等式,一但發現有不滿足三角形不等式的情況,則更新對應的D值。最後求出來的D1到Dn就是源點到每個點的最短路徑長度。
如果我們一開始初始化D1、D2、……、Dn的值分別為M1、M2、……、Mn,則由於它們全都滿足三角形不等式(我們剛才已經假設M1到Mn是一組合法的解),則Bellman-Ford演算法不會再更新任合D值,則最後得出的解就是M1、M2、……、Mn。
好了,現在知道了,初始值無窮大時,算出來的是D1、D2、……、Dn;初始值比較小的時候算出來的則是M1、M2、……、Mn。大家用的是同樣的演算法,同樣的計算過程,總不可能初始值大的算出來的結果反而小吧。所以D1、D2、……、Dn就是M1、M2、……、Mn。
那麼如果在一個未知數定死的情況下,要求其它所有未知數的最小值怎麼辦?只要反過來求最長路徑就可以了。最長路徑中的三角不等式與最短路徑中相反:
d(v) >= d(u) + w(u, v)
也就是 d(v) - d(u) >= w(u, v)
所以建圖的時候要先把所有不等式化成大於等於號的。其它各種過程,包括證明為什麼解出的是最小值的證法,都完全類似。
SPFA程式碼:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int MAX=110;
const int INF=(1<<30);
struct node
{
int u,next,f;
}edge[MAX*MAX];
int pre[MAX],dis[MAX],cnt[MAX];
bool vis[MAX];
int N,M,num;
void init()
{
num=0;
memset(pre,-1,sizeof(pre));
}
void add_edge(int x,int y,int f)
{
edge[num].u=y;
edge[num].next=pre[x];
edge[num].f=f;
pre[x]=num++;
}
bool SPFA()
{
for(int i=0;i<=N+1;i++)
{
dis[i]=INF;
vis[i]=false;
cnt[i]=0;
}
dis[N+1]=0;
vis[N+1]=true;
cnt[N+1]=1;
queue<int> q;
q.push(N+1);
while(!q.empty())
{
int t=q.front();
q.pop();
vis[t]=false;
for(int i=pre[t];i!=-1;i=edge[i].next)
{
int v=edge[i].u,w=edge[i].f;
if(dis[v]>dis[t]+w)
{
dis[v]=dis[t]+w;
if(!vis[v])
{
vis[v]=true;
q.push(v);
if(++cnt[v]>N+1)
return true;
}
}
}
}
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(cin>>N,N)
{
int s,n,k;
char o[5];
init();
scanf("%d",&M);
for(int i=0;i<M;i++)
{
scanf("%d%d%s%d",&s,&n,o,&k);
if(o[0]=='g')
add_edge(s+n,s-1,-1-k);
else add_edge(s-1,s+n,k-1);
}
for(int i=0;i<=N;i++)
add_edge(N+1,i,0);
if(SPFA())
printf("successful conspiracy\n");
else printf("lamentable kingdom\n");
}
return 0;
}
Bellman程式碼:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int MAX=110;
const int INF=(1<<30);
struct node
{
int u,v,next,f;
}edge[MAX*MAX];
int pre[MAX],dis[MAX],cnt[MAX];
bool vis[MAX];
int N,M,num;
void init()
{
num=0;
memset(pre,-1,sizeof(pre));
}
void add_edge(int x,int y,int f)
{
edge[num].u=y;
edge[num].v=x;
edge[num].next=pre[x];
edge[num].f=f;
pre[x]=num++;
}
bool Bellman()
{
memset(dis,0,sizeof(dis));
for(int i=1;i<=N;i++)
for(int j=0;j<num;j++)
{
int v=edge[j].v,u=edge[j].u,w=edge[j].f;
if(dis[u]>dis[v]+w)
{
dis[u]=dis[v]+w;
if(i>=N)
return true;
}
}
return false;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(cin>>N,N)
{
int s,n,k;
char o[5];
init();
scanf("%d",&M);
for(int i=0;i<M;i++)
{
scanf("%d%d%s%d",&s,&n,o,&k);
if(o[0]=='g')
add_edge(s+n,s-1,-1-k);
else add_edge(s-1,s+n,k-1);
}
for(int i=0;i<=N;i++)
add_edge(N+1,i,0);
if(Bellman())
printf("successful conspiracy\n");
else printf("lamentable kingdom\n");
}
return 0;
}
相關文章
- POJ 3169(Bellman-Ford演算法,差分約束系統)演算法
- POJ 3159-Candies(差分約束系統-SPFA+鄰接表)
- 淺談差分約束系統
- 差分約束系統詳解
- 差分約束
- uva 11478 最短路徑問題(負環,差分約束系統)
- POJ 2983-Is the Information Reliable?(差分約束系統)ORM
- POJ 1364-King(差分約束系統)
- 演算法學習之路|差分約束系統演算法
- 差分約束系統+poj1201
- Bellman - Ford, SPFA 學習筆記(含有負權的單源最短路徑)筆記
- 差分約束學習筆記筆記
- Day2 尤拉路,拓撲排序和差分約束排序
- POJ 3169-Layout(差分約束系統-入門裸題)
- 差分約束的一些理解
- 資料遷移判斷非空約束
- [演算法學習筆記] 差分約束演算法筆記
- 判斷負環模板
- JS的判斷語句:判斷、迴圈JS
- Activiti判斷流程是否結束
- shell判斷系統路徑中是否存在空格
- Bellman Ford+SPFA佇列優化(路徑還原 輸出最短路的路徑)佇列優化
- java判斷迴文數Java
- Swift,迴圈及判斷Swift
- Atcoder ABC 216 G 01Sequence 題解 [ 藍 ] [ 差分約束 ]
- js判斷手機系統JS
- JavaScript判斷數字正負數JavaScript
- Javaweb-約束的分類JavaWeb
- ACM之判斷迴文數ACM
- python如何判斷迴文Python
- 判斷是否為迴文字元字元
- 5.判斷和迴圈
- 【SQL】15 SQL 約束(Constraints)、NOT NULL 約束、UNIQUE 約束、PRIMARY KEY 約束、FOREIGN KEY 約束、CHECK 約束、DEFAULT約束SQLAINull
- 時序分析:基礎知識整理(三)差分轉單端的約束等
- 33判斷字串是否為迴文字串
- 如何判斷作業系統大小端作業系統
- JavaScript判斷系統和瀏覽器JavaScript瀏覽器
- C# File.Exists 判斷系統檔案,警惕32位和64位的差異C#