差分約束系統+SPFA/Bellman判斷負權迴路+uva515

u010660276發表於2013-12-22

轉自: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;
}


相關文章