差分约束系统
首先,我们先介绍一下什么是差分约束系统。
定义:如果一个系统由 nnn 个变量和 mmm 个约束条件组成,形成 mmm 个形如ai−aj≤ka_i-a_j \leq kai−aj≤k的不等式 。 i,j∈[1,n]i,j\in \left [ 1,n \right ]i,j∈[1,n] 且 kkk为常数。
(不绝对,若为ai−aj≥ka_i-a_j \geq kai−aj≥k,即是最长路)
可以理解为:差分约束是求解关于一组变量的特殊不等式组的方法。(即: 线性规划问题 )
举个例子:
在样例中,如果我们把不等式等价成矩阵的形式,即:
[1−1001−110−1][x1x2x3]≤[3−21]
\begin{bmatrix}
1& -1& 0 \\
0& 1& -1 \\
1& 0& -1
\end{bmatrix}
\begin{bmatrix}
x_1\\
x_2\\
x_3
\end{bmatrix}
\leq
\begin{bmatrix}
3\\
-2\\
1
\end{bmatrix}
⎣⎡101−1100−1−1⎦⎤⎣⎡x1x2x3⎦⎤≤⎣⎡3−21⎦⎤
关于上面这个不等式的解,可以是x=(5,3,5)x=\left( 5,3,5\right)x=(5,3,5),也可以是x=(0,−2,0)x=\left( 0,-2,0\right)x=(0,−2,0)
观察两个解,不难看出:两解相差一个定值 5。
所以,我们可以得出一个结论:
设x=(x1,x2,x3,...,xn)x=\left( x_1,x_2,x_3,...,x_n\right)x=(x1,x2,x3,...,xn)是不等式的一个解。设ddd为任意常数,则x+d=(x1+d,x2+d,x3+d,...,xn+d)x+d=\left( x_1+d,x_2+d,x_3+d,...,x_n+d\right)x+d=(x1+d,x2+d,x3+d,...,xn+d)也是该不等式的一个解。
说话讲道理,证明:
对于每一个xi,xjx_i,x_jxi,xj,我们有(xi+d)−(xj+d)=xi−xj\left(x_i+d\right)-\left(x_j+d\right)=x_i-x_j(xi+d)−(xj+d)=xi−xj。因此若xxx满足不等式,x+dx+dx+d也满足。
(这也是这一题special judge的原因)
约束图
刚刚分析了差分约束系统。那这一问题和图论又有什么关系呢?
敏感的小伙伴在看到上面的矩阵时,应该就反应过来了。
这是一个有向无环图的关联矩阵。
(即: 从减数指向被减数 )
再将边的权重赋值为不等式的常数kkk。
最后构造一个点(起点),从其出发可以到达所有其他节点,权值赋为0。
即构成了差分约束系统的约束图。
求解
对于约束图,若图中不存在负环。则起点到每个边的最短路权值,即为差分约束系统的一个可行解。
若存在负环,则无解。
代码如下(SPFA判负环)
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MAXN 500000
using namespace std;
int adj[MAXN],cnt=0,num[MAXN];
int dis[MAXN],ori=0,n,m;;
bool vis[MAXN];
queue < int > q;
struct EDGE
{
int to,nxt,val;
} e[MAXN];
void addedge(int u,int v,int w)
{
e[++cnt].to=v; e[cnt].val=w; e[cnt].nxt=adj[u]; adj[u]=cnt;
}
bool SPFA()
{
for(int i=1;i<=n;++i) dis[i]=(i==ori ? 0 : INF);
q.push(ori); vis[ori]=1; ++num[ori];
while(!q.empty())
{
int u=q.front(); q.pop(); vis[u]=0;
for(int i=adj[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].val)
{
dis[v]=dis[u]+e[i].val;
if(!vis[v])
{
vis[v]=1;q.push(v);
++num[v];
if(num[v]>n) return 0;
}
}
}
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) addedge(0,i,0);
for(int i=1;i<=m;++i)
{
int u,v,w; scanf("%d%d%d",&u,&v,&w);
addedge(v,u,w);
}
if(SPFA())
for(int i=1;i<=n;++i) printf("%d ",dis[i]);
else
printf("NO");
return 0;
}
补充:
差分约束在实际应用中很丰富。可以是最短路径,也可以是最长路径。
在实际问题中,要尽可能的把题目中个不等关系找全。(如:0≤x2−x1≤10 \leq x_2-x_1\leq 10≤x2−x1≤1,前半个容易丢)
参考资料:《算法导论》