差分约束系统
芜湖~ 图论和不等式的梦幻联动
如果一个系统由 n n n个变量和 m m m个约束条件组成,形成 m m m个形如 a i − a j ≤ k ai-aj≤k ai−aj≤k 的不等式( i , j ∈ [ 1 , n ] i,j∈[1,n] i,j∈[1,n],k为常数),则称其为差分约束系统 (system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
求解差分约束系统,可以转化成图论的单源最短路径问题。
例: 对于一组由4个变量4个约束条件构成的不等式
{
x
3
−
x
1
≤
2
x
4
−
x
3
≤
−
1
x
3
−
x
2
≤
−
3
x
2
−
x
4
≤
5
\begin{cases} x3-x1≤2\\ x4-x3≤-1\\ x3-x2≤-3\\ x2-x4≤5 \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧x3−x1≤2x4−x3≤−1x3−x2≤−3x2−x4≤5
求一组可行解
首先我们易得,当此组不等式有解时,必然存在无数组可行解,否则无解。
假设我们已知一组可行解 {
x
1
,
x
2
,
.
.
.
,
x
n
x1,x2,...,xn
x1,x2,...,xn},则 {
x
1
+
k
,
x
2
+
k
,
.
.
.
,
x
n
+
k
x1+k,x2+k,...,xn+k
x1+k,x2+k,...,xn+k}, …, {
x
1
+
m
k
,
x
2
+
m
k
,
.
.
.
,
x
n
+
m
k
x1+mk,x2+mk,...,xn+mk
x1+mk,x2+mk,...,xn+mk}
(
k
,
m
∈
R
)
(k,m∈R)
(k,m∈R) 均为可行解
问题转化
对于不等式组:
{
B
−
A
≤
c
.
.
.
①
C
−
B
≤
a
.
.
.
②
C
−
A
≤
b
.
.
.
③
\begin{cases} B - A ≤ c...①\\ C - B ≤ a...②\\ C - A ≤ b...③ \end{cases}
⎩⎪⎨⎪⎧B−A≤c...①C−B≤a...②C−A≤b...③
① + ② 得
C
−
A
≤
a
+
c
.
.
.
④
C-A ≤ a+c...④
C−A≤a+c...④
由③④ 根据不等式的基本性质
可得
C
−
A
≤
m
i
n
(
a
+
c
,
b
)
C-A≤min(a+c, b)
C−A≤min(a+c,b)
即
m
a
x
(
C
−
A
)
=
m
i
n
(
a
+
c
,
b
)
max(C-A)=min(a+c,b)
max(C−A)=min(a+c,b)
可以发现该不等式正好对应下图的最短路问题
从公式入手,我们比较一下这两个问题涉及到的三角不等式的相似性:
C
−
A
≤
m
i
n
(
a
+
c
,
b
)
C-A≤min(a+c, b)
C−A≤min(a+c,b)
d
i
s
A
,
C
≤
d
i
s
A
,
B
+
w
a
,
c
dis_{A,C}≤dis_{A,B}+w_{a,c}
disA,C≤disA,B+wa,c
是不是一模一样
因此对于不等式 x v − x u ≤ w x_v-x_u≤w xv−xu≤w 我们可以建立一条由节点 u u u指向节点 v v v边权为 w w w的边
//u v w
1 3 2
3 4 -1
2 3 -3
4 2 5
建图
对于非连通图,例如本张图,在解决差分约束问题时,需要增加一个超级源点0 ,建立由0点到其他各点边权为0的n条边,将问题转换为单源最短路。
对于连通图,(本身就可以当做单源最短路问题),我们可以以任意一点为起点,记录该点到其他点的最短路,dis[]则为一组可行解。
如此一来,我们记录源点到各个点的最短路径:
d
i
s
[
1
]
=
0
,
d
i
s
[
2
]
=
0
,
d
i
s
[
3
]
=
−
1
,
d
i
s
[
4
]
=
−
4
dis[1]=0 ,dis[2]=0,dis[3]=-1,dis[4]=-4
dis[1]=0,dis[2]=0,dis[3]=−1,dis[4]=−4
其所构成的一组解为:
{
x
1
=
0
,
x
2
=
0
,
x
3
=
−
1
,
x
4
=
−
4
x_1=0,x_2=0,x_3=-1,x_4=-4
x1=0,x2=0,x3=−1,x4=−4} 。
因此我们只需建图,跑一遍最短路即可。求解最短路可以使用spfa或bellman-ford算法,由于存在负权边,不能使用dijkstra。
特判无解
当图中存在负权环时,源点到某点的最短路径变为负无穷,显然不成立,则不等式组无解。
因此在跑最短路的过程中我们需要判负环。即在spfa中,当某个节点被入队n次,则存在负环。
P3358负环模板题 洛谷传送门
重点强调判负环的时候是在入队之后判 不是在松弛操作的时候判!!!!!!
if(!vis[v]){
cnt[v]++; //点v入队次数
vis[v]=1; //访问标记 注意在取队首的时候要释放标记!!!!
q.push(v); //入队
if(cnt[v]>=n) { //判负环
printf("NO");
return ;
}
}
代码
题目描述
给出一组包含
m
m
m 个不等式,有
n
n
n 个未知数的形如:
{
x
c
1
−
x
c
1
′
≤
y
1
x
c
2
−
x
c
2
′
≤
y
2
.
.
.
x
c
m
−
x
c
m
′
≤
y
m
\begin{cases} x_{c1}-x_{c1'}\le y_1& \\ x_{c2}-x_{c2'}\le y_2 & \\ ...\\ x_{cm}-x_{cm'}\le y_m \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧xc1−xc1′≤y1xc2−xc2′≤y2...xcm−xcm′≤ym
的不等式组,求任意一组满足这个不等式组的解。
输入格式
第一行为两个正整数
n
,
m
n,m
n,m,代表未知数的数量和不等式的数量。
接下来
m
m
m 行,每行包含三个整数
c
,
c
′
,
y
c,c',y
c,c′,y, 代表一个不等式
x
c
−
x
c
′
≤
y
x_c-x_c'\le y
xc−xc′≤y
输出格式
一行,
n
n
n 个数,表示
x
1
,
x
2
,
.
.
.
,
x
n
x_1,x_2,...,x_n
x1,x2,...,xn的一组可行解,如果有多组解,请输出任意一组,无解请输出 NO
。
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define inf 0x3f3f3f3f
#define maxn 6000
#define maxm 11000
using namespace std;
int n,m,ct,flag;
int head[maxn],vis[maxn],dis[maxn],cnt[maxn];
struct node{
int to,next,w;
}e[maxm];
//没啥好解释的
void add(int u,int v,int w){
ct++;
e[ct].w =w;
e[ct].to =v;
e[ct].next =head[u];
head[u]=ct;
}
queue<int>q;
void spfa(int s){
for(int i=1;i<=n;i++){
dis[i]=inf;
}
q.push(s);
vis[s]=1; dis[s]=0;
while(!q.empty() ){
int t=q.front() ; q.pop(); vis[t]=0;//注意释放标记啊啊啊
for(int i=head[t];i!=-1;i=e[i].next ){
int v=e[i].to;
if(dis[v]>dis[t]+e[i].w){
dis[v]=dis[t]+e[i].w; // 松弛操作
if(!vis[v]){
cnt[v]++;
vis[v]=1;
q.push(v);
if(cnt[v]>=n) { //判负环
printf("NO");
flag=1;
return ;
}
}
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
memset(head,-1,sizeof head); // 初始化 注意点的编号是从0开始
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(v,u,w); //注意边的指向
}
for(int i=1;i<=n;i++){
add(0,i,0);// 超级源点建边
}
spfa(0); //以0为起点
if(flag==1) return 0;
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
其实就跟裸的最短路没啥区别。