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;
}