[模板][最短路] 差分约束系统

差分约束系统

定义 来自某度百科

如果一个系统由 \(n\) 个变量和 \(m\) 个约束条件组成,形成 \(m\) 个形如 \(a_i-a_j≤k\) 的不等式 (\(i,j\in[1,n]\), \(k\)为常数),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。

简单来说,给你一组不等式:

\[\left \{ \begin{array}{} x_1 - x_2 \le c_1 \\ x_2 - x_3 \le c_2 \\ \ \ \ \ \ \cdots \\ x_i - x_j \le c_k \end{array}\right. \]

求这组不等式的可行解。

解法

该问题等价于在一个有向图上求最短/长路,

首先建立一个超级源点, 向每个节点链接一条边权为 \(0\) 的边,

然后开始转化:

  1. 转化为最

    \[\]

    x_1 \le c_1 + x_2\
    x_2 \le c_2 + x_3\
    \ \ \ \ \ \cdots \
    x_i \le c_k + x_j
    \end{array}\right.

    \[\]

  2. 转化为最

    \[\]

    x_1 \ge -c_1 + x_2\
    x_2 \ge -c_2 + x_3\
    \ \ \ \ \ \cdots \
    x_i \ge -c_k + x_j
    \end{array}\right.

    \[\]

我们要做的就是从大于号左边(小于号右边)向大于号右边(小于号左边)连接一条边权为 \(c_i\)(\(-c_i\)) 的边,跑最短路即可。

无解?

联系最短路,发现当图上有负环无解,

所以跑 \(SPFA\) 即可。

模板

Luogu P5960 [模板] 差分约束算法

// 注释掉的是跑最长路找最小正数可行解的方法
# include <iostream>
# include <cstdio>
# include <queue>
# include <cstring>
# define MAXN 5005
# define MAXM 10005

using namespace std;

struct edge{
	int u, v, next, w;
}e[MAXM];
int hd[MAXM], cntE;

void AddE(int u, int v, int w){
	e[++cntE].u = u, e[cntE].v = v, e[cntE].next = hd[u], hd[u] = cntE;
	e[cntE].w = w;
}

//
int dis[MAXN], cntQ[MAXN];
bool inQ[MAXN];

bool SPFA(int from, int lim){
	queue<int>Q;
	memset(inQ, 0, sizeof(inQ));
	memset(dis, 0x3f, sizeof(dis));
    // for(int i = 1; i <= lim-1; i++){
    //     dis[i] = -100000;
    // }
	
	dis[from] = 0;
	Q.push(from); inQ[from] = 1;

	while(!Q.empty()){
		int now = Q.front(); Q.pop();
		inQ[now] = 0;

		for(int i = hd[now]; i; i = e[i].next){
            if(dis[e[i].v] > dis[now] + e[i].w){
			// if(dis[e[i].v] < dis[now] + e[i].w){
				dis[e[i].v] = dis[now] + e[i].w;

				if(!inQ[e[i].v]){
					Q.push(e[i].v); inQ[e[i].v] = 1;
					cntQ[e[i].v] += 1;

					if(cntQ[e[i].v] == lim){
						return false;
					}
				}
			}
		}
	}

	return true;
}

//
int main(){
	int n, m;
	cin>>n>>m;

	for(int i = 1, u, v, w; i <= m; i++){
		cin>>u>>v>>w;
		AddE(v, u, w); // 注意这里的连边方向,应该是从大于连向小于
        // AddE(u, v, -w);
	}
	
	for(int i = 1; i <= n; i++){
		AddE(0, i, 0);
	} // 建立超级源点

	bool chk = SPFA(0, n+1);
	
	if(chk){
		for(int i = 1; i <= n; i++){
			cout<<dis[i]<<' ';
		}
	}
	else{
		cout<<"NO";
	}

	return 0;
}

posted @ 2020-07-30 16:12  ChPu437  阅读(80)  评论(3编辑  收藏  举报