1. 程式人生 > 其它 >poj3169:Layout——差分約束+Bellman-Ford演算法

poj3169:Layout——差分約束+Bellman-Ford演算法

差分約束系統

參考:https://www.cnblogs.com/genius777/p/9163103.html

差分約束系統只是對最短路演算法的一種應用,沒有什麼新的演算法,只是對於具體問題的建圖方法的確定

差分約束系統解決的問題是不等式組的求解:

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的得到統一的不等號方向的不等式組。

這個不等式組一定是無解和無數解兩種情況,因為如果是存在任意一組解,{x1,x2,x3,x4,x5},我們都可以通過{x1+k,x2+k,x3+k,x4+k,x5+k}得到一個新的解。所以解的個數是無數的。

因為每個數都加k,他們任意兩個數之間的差是不變的,所以對於不等式沒有影響。

與最短路聯絡

B - A <= c     (1)
C - B <= a     (2)
C - A <= b     (3)

如果要求C-A的最大值,可以知道max(C-A)= min(b,a+c),而這正對應了下圖中C到A的最短路。

差分約束建圖技巧

1.對於一個全部都是<=號的不等式組,我們可以將每個式子轉化為Xi<=Xj+W(i,j),那麼就建一條邊,Xj->Xi=W(i,j),然後利用最短路徑解決問題,在x0定死的情況下,求得最小值

2.對於一個全部都是>=號的不等式組,我們可以將每個式子轉化為Xi>=Xj+W(i,j),那麼就建一條邊,Xj->Xi=W(i,j),然後利用最長路徑解決問題,在x0定死的情況下,求得最大值

如果dis[Xi]為inf或-inf,那麼Xi為任意解

如果求最短路的過程中出現負環,那麼說明該不等式組無解

題目描述

需要放置n頭牛,AL和BL兩頭牛之間存在“喜歡”關係,則它們之間的距離不能超過DL,有些AD和BD兩頭牛之間存在“不喜歡”關係,則它們之間的距離不能超過DD。所有牛編號從1~N,求1號牛和N號牛之間的最大距離,無解輸出-1,距離無限大輸出-2

分析

根據條目分析,可以得出約束條件為,d[i]為第i頭牛放置的位置

  • d[i+1] + 0 >= d[i],n頭牛按編號順序依次放置,且可放置在同一位置
  • d[AL] + DL >= d[BL],“喜歡”關係的兩頭牛之間距離不超過DL
  • d[BD] - DD >= d[AD],“不喜歡”關係兩頭牛之間的距離不小於DD

將原問題轉化成最短路問題,

  • 建立一條從i+1到i,權值為0的邊
  • 建立一條從AL到BL,權值為DL的邊
  • 建立一條從BD到AD,權值為-DD的邊

原問題d[N] - d[1]的最大值,對應頂點1到頂點N的最短距離(不理解可看上文差分約束與最短路徑關係部分),由於圖中存在負權重的邊,不能使用Dijkstra演算法,可採用Bellman-Ford演算法求解。

程式碼

#include <stdio.h>
#include <iostream>
#include <climits>
#include <vector>
using namespace std;

const int MAXN = 1000 + 10;
const int MAXM = 10000 + 10;
const int INF = INT_MAX;

int N, ML, MD;
int AL[MAXM], BL[MAXM], DL[MAXM];   //'喜歡'關係約束
int AD[MAXM], BD[MAXM], DD[MAXM];   //'不喜歡'關係約束
int dist[MAXN];     //最短距離

//鄰接表儲存圖
struct Edge{
    int to, weight;
    Edge(int t, int w): to(t), weight(w) {}
};
vector<Edge> G[MAXN];

//根據差分約束建立圖
void BuildGraph(){
    //從i+1->i權值為0的邊
    for (int i = 1; i < N; ++i) {
        G[i+1].push_back(Edge(i, 0));
    }

    /*
         * BL - AL <= DL
         * AL + DL >= BL
         * 從AL到BL權值為DL的邊 */
    for (int i = 0; i < ML; ++i) {
        G[AL[i]].push_back(Edge(BL[i], DL[i]));
    }

    /*
         * BD - AD >= DD
         * BD - DD >= AD
         * 從BD到AD權值-DD的邊*/
    for (int i = 0; i < MD; ++i) {
        G[BD[i]].push_back(Edge(AD[i], -DD[i]));
    }
}

void Bellman_Ford(){
    fill(dist, dist + MAXN, INF);
    dist[1] = 0;

    //迴圈N-1次
    for (int i = 0; i < N - 1; ++i) {
        //對所有邊進行鬆弛操作
        for (int j = 1; j <= N; ++j) {
            for (int k = 0; k < G[j].size(); ++k) {
                Edge e = G[j][k];
                if (dist[j] < INF && dist[e.to] > dist[j] + e.weight){
                    dist[e.to] = dist[j] + e.weight;
                }
            }
        }
    }

    //再遍歷一次所有邊(第N次迴圈)
    bool ngloop = false;
    for (int j = 1; j <= N; ++j) {
        for (int k = 0; k < G[j].size(); ++k) {
            Edge e = G[j][k];
            //如果本次有更新,則存在負環
            if (dist[j] < INF && dist[e.to] > dist[j] + e.weight){
                dist[e.to] = dist[j] + e.weight;
                ngloop = true;
            }
        }
    }

    if (ngloop){
        printf("-1\n");
    }
    else{
        int ans = dist[N];
        if (ans == INF){
            ans = -2;
        }
        printf("%d\n", ans);
    }
}

int main(){
    scanf("%d%d%d", &N, &ML, &MD);
    for (int i = 0; i < ML; ++i) {
        scanf("%d%d%d", &AL[i], &BL[i], &DL[i]);
    }
    for (int i = 0; i < MD; ++i) {
        scanf("%d%d%d", &AD[i], &BD[i], &DD[i]);
    }

    BuildGraph();

    Bellman_Ford();

    return 0;
}