差分约束系统 ( s y s t e m o f d i f f e r e n c e c o n s t r a i n t s ) (system of difference constraints) (systemofdifferenceconstraints) ,是求解关于一组变数的特殊
不等式组之方法。
如果一个系统由 n n n 个变量和 m m m 个约束条件组成,其中每个约束条件形如 x i − x j ≤ b k ( i , j ∈ [ 1 , n ] , k ∈ [ 1 , m ] ) x_i - x_j \le b_k (i, j∈[1, n], k∈[1, m]) xi−xj≤bk(i,j∈[1,n],k∈[1,m]),则称其为差分约束系统。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
通俗一点地说,差分约束系统就是一些不等式的组,而我们的目标是通过给定的约束不等式组求出最大值或者最小值或者差分约束系统是否有解。
这个不等式组有两种情况:
差分约束系统的解只有以下两种情况:
先来看一组数据:
{ x 1 − x 2 ≤ 1 x 2 − x 3 ≤ 2 x 3 − x 1 ≤ − 1 \begin{cases}x_1 - x_2 \le 1\\x_2 - x_3 \le 2\\x_3 - x_1 \le -1\end{cases} ⎩⎪⎨⎪⎧x1−x2≤1x2−x3≤2x3−x1≤−1
经过暴力枚举依靠我们高超的数感不难得到有其中两组解:
{ x 1 = 3 x 2 = 2 x 3 = 0 \begin{cases}x_1 = 3\\x_2 = 2\\x_3 = 0\end{cases} ⎩⎪⎨⎪⎧x1=3x2=2x3=0
{ x 1 = 10 x 2 = 9 x 3 = 7 \begin{cases}x_1 = 10\\x_2 = 9\\x_3 = 7\end{cases} ⎩⎪⎨⎪⎧x1=10x2=9x3=7
这两个解的联系?第二组解中的每个元素比第一组解中的相应元素大 7 7 7。
这并不是巧合:若 x = ( x 1 , x 2 , … , x n ) x = ( x1, x2, …, xn ) x=(x1,x2,…,xn) 是一个差分约束系统 A x ≤ b Ax ≤ b Ax≤b 的一个解, d d d 为任意常数。则 x + d = ( x 1 + d , x 2 + d , … , x n + d ) x + d = ( x1 + d, x2 + d, …, xn + d ) x+d=(x1+d,x2+d,…,xn+d) 也是该系统 A x ≤ b Ax ≤ b Ax≤b 的解。
对于每个 x i x_i xi和 x j x_j xj,有 ( x j + d ) − ( x i + d ) = x j − x i (x_j + d) - (x_i + d) = x_j - x_i (xj+d)−(xi+d)=xj−xi 。因此,若 x x x 满足 A x ≤ b Ax≤b Ax≤b,则 x + d x + d x+d 也同样满足,因此,差分约束系统若有解,则必有无数组解。
if (dis[v] > dis[u] + w(u, v))
{
dis[v] = dis[u] + w(u, v);
}
**差分约束的难点在于建图,而建图的难点在于找出题目中隐藏的不等式。能使用
差分约束解决的题目,一定在题面中体现了若干个形式相同或不同的不等式。**
建图函数————》》(链式前向星)
inline void add(int a, int b, int w)
{
e[++tot].next = head[a];
e[tot].to = b;
e[tot].w = w;
head[a] = tot;
}
建图函数————》》(vector)
d[a].push_back({b,k});
首先根据题目的要求进行不等式组的统一化。
%%%注意建图函数 a d d ( ) add() add() 中参数的位置
将不等式全部化成 x i – x j ≤ k xi \ – \ xj \le k xi – xj≤k 的形式, 这样建立 j → i j \to i j→i ,权值为 k k k的边。
{ a = b 建 双 向 边 ⟹ a d d ( a , b , 0 ) & a d d ( b , a , 0 ) a > b ⟹ a − b > 0 ⟹ b − a ≤ 1 ⟹ a d d ( a , b , 1 ) a < b ⟹ a − b ≤ − 1 ⟹ a d d ( b , a , − 1 ) a ≥ b + k ⟹ b − a ≤ k ⟹ a d d ( a , b , k ) a ≤ b + k ⟹ a − b ≤ k ⟹ a d d ( b , a , k ) 此 处 省 略 多 种 毒 瘤 . . . . . . \begin{cases}a=b 建双向边 \Longrightarrow add(a,b,0) \ \& \ add(b,a,0)\\ a>b \Longrightarrow a-b>0 \Longrightarrow b-a \le 1 \Longrightarrow add(a,b,1)\\ a⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧a=b建双向边⟹add(a,b,0) & add(b,a,0)a>b⟹a−b>0⟹b−a≤1⟹add(a,b,1)a<b⟹a−b≤−1⟹add(b,a,−1)a≥b+k⟹b−a≤k⟹add(a,b,k)a≤b+k⟹a−b≤k⟹add(b,a,k)此处省略多种毒瘤......
。。。。。。
如果是左式 a − b a-b a−b ,那么 a d d ( b , a , k ) add(b,a,k) add(b,a,k) ;如果是 b − a b-a b−a ,那么 a d d ( a , b , k ) add(a,b,k) add(a,b,k) ;
// v e c t o r vector vector 同理
//dis[i]设为0x3f
if(dis[e[i].to]>dis[tmp]+e[i].w)
{
dis[e[i].to]=dis[tmp]+e[i].w;
}
//----------
//a-b<=k
//add(b,a,k);
//d[b].push_back({a,k});
因为建出的图可能不连通,所以我们需要虚拟一个不存在的值 x 0 x0 x0,并对于每一个
点 x n xn xn,我们都要多建一条有向边 x n − x 0 < = 0 xn - x0 <= 0 xn−x0<=0 , a d d ( 0 , i , 0 ) add(0,i,0) add(0,i,0),这样不会破
坏任何不等式,且使原图连通。我们通常称之为 超 级 源 点 超级源点 超级源点 。
一般输出答案分为两种:
如何判断是否有满足解
即是判断图中是否存在负环
不严谨证明:对于某个点 x x x,与之相连的边至少有 n n n(总点数) − 1 -1 −1种,
所以我们可以定义一个 f h fh fh 数组记录每次点 x x x 入队的次数,
如果次数 > n − 1 >n-1 >n−1 ,即图中存在负环。
inline void Spfa(int x)
{
init();
queue<int> q;
dis[x] = 0;
vis[x] = 1;
fh[x]++;
q.push(x);
while (!q.empty())
{
int tmp = q.front();
q.pop();
vis[tmp] = 0;
for (register int i = head[tmp]; i; i = e[i].next)
{
if (dis[e[i].to] > dis[tmp] + e[i].w)
{
dis[e[i].to] = dis[tmp] + e[i].w;
if (!vis[e[i].to])
{
q.push(e[i].to);
vis[e[i].to] = 1;
fh[e[i].to]++; //---
if (fh[e[i].to] > n)
{ //---
flag = 1; //---
return; //---
} //---
}
}
}
}
}
上述的判断负环方法最坏情况为 O ( N 2 ) O(N^2) O(N2),在比赛中极大几率会TLE
所以接下来给大家推荐几种优化方法
优化思路:将原队列改成双端队列,对要加入队列的点 p,如果 dist[p] 小于队头元素 u 的 dist[u],将其插入到队头,否则插入到队尾。
inline void Spfa(int x)
{
deque<int> q; //---
dis[x] = 0;
vis[x] = 1;
q.push_back(x);
while (!q.empty())
{
int tmp = q.front();
q.pop_front();
vis[tmp] = 0;
for (register int i = head[tmp]; i; i = e[i].next)
{
if (dis[e[i].to] > dis[tmp] + e[i].w)
{
dis[e[i].to] = dis[tmp] + e[i].w;
if (!vis[e[i].to])
{
if (!q.empty() && dis[e[i].to] > dis[tmp]) //---
q.push_back(e[i].to); //---
else //---
q.push_front(e[i].to); //---
vis[e[i].to] = 1;
fh[e[i].to]++;
if (fh[e[i].to] > n)
{
cout << "No";
exit(0);
}
}
}
}
}
}
便于寻找图中的负环,不建议用,会莫名其妙TLE!~~
不过这真的是神器!!!
对于20组数据
SLF优化 —— 4s
DFS优化 —— 172ms
神器啊!!!b——
bool spfa(int x)
{
vis[x] = true;
for (int i = 0; i < g[x].size(); i++)
{
int to = g[x][i].to;
int w = g[x][i].w;
if (dis[x] + w < dis[to])
{
dis[to] = dis[x] + w;
if (vis[to])
return false;
if (!spfa(to))
return false;
}
}
vis[x] = false;
return true;
} //vector
给出一组包含 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_{c_1} - x_{c'_1} \le y_1\\x_{c_2} - x_{c'_2} \le y_2\\\cdots\\x_{c_m} - x_{c'_m} \le y_m\end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧xc1−xc1′≤y1xc2−xc2′≤y2⋯xcm−xcm′≤ym
的不等式组,求任意一组满足这个不等式组的解。
第一行为两个正整数 n , m n,m n,m,代表未知数的数量和不等式的数量。
接下来 m m m 行,每行包含三个整数 c c c, c ′ c' c′, y y y,代表一个不等式 x c − x c ′ ≤ y x_c - x_c' \le y xc−xc′≤y
一行, n n n 个数,表示 x 1 x_1 x1 , x 2 ⋯ x n x_2 \cdots x_n x2⋯xn 的一组可行解,如果有多组解,请输出任意
一组,无解请输出 N O NO NO
3 3
1 2 3
2 3 -2
1 3 1
5 3 5
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 5 × 1 0 3 , − 1 0 4 ≤ y ≤ 1 0 4 , 1 ≤ c , c ′ ≤ n , c ≠ c ′ 1 \le n, m \le 5 \times 10^3, -10^4 \le y \le10^4, 1 \le c, c' \le n, c \ne c' 1≤n,m≤5×103,−104≤y≤104,1≤c,c′≤n,c=c′
#include
using namespace std;
const int maxn = 1e5 + 99;
int n, m, head[maxn], dis[maxn], vis[maxn], tot, fh[maxn], flag;
inline int Read()
{
int x = 0, f = 1;
char c = getchar();
while (c > '9' || c < '0')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
struct Edge
{
int to, next, w;
} e[maxn];
inline void init()
{
memset(dis, 0x3f, sizeof(dis));
memset(vis, 0, sizeof(vis));
memset(fh, 0, sizeof(fh));
}
inline void add(int a, int b, int w)
{
e[++tot].next = head[a];
e[tot].to = b;
e[tot].w = w;
head[a] = tot;
}
void Spfa()
{
init();
queue<int> q;
dis[0] = 0;
vis[0] = 1;
q.push(0);
while (!q.empty())
{
int tmp = q.front();
q.pop();
vis[tmp] = 0;
for (int i = head[tmp]; i; i = e[i].next)
{
if (dis[e[i].to] > dis[tmp] + e[i].w)
{
dis[e[i].to] = dis[tmp] + e[i].w;
if (!vis[e[i].to])
{
q.push(e[i].to);
vis[e[i].to] = 1;
fh[e[i].to]++;
if (fh[e[i].to] > n)
{
flag = 1;
return;
}
}
}
}
}
}
int main()
{
cin >> n >> m;
//init();
for (int i = 1, u, v, w; i <= m; i++)
{
cin >> u >> v >> w;
add(v, u, w);
}
for (int i = 1; i <= n; i++)
add(0, i, 0);
Spfa();
if (!flag)
{
for (int i = 1; i <= n; i++)
cout << dis[i] << " ";
}
else
cout << "NO";
return 0;
}
小 K 在 MC 里面建立很多很多的农场,总共 n n n 个,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共 m m m 个),以下列三种形式描述:
农场 a a a 比农场 b b b 至少多种植了 c c c 个单位的作物;
农场 a a a 比农场 b b b 至多多种植了 c c c 个单位的作物;
农场 a a a 与农场 b b b 种植的作物数一样多。
但是,由于小 K 的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。
第一行包括两个整数 n n n 和 m m m,分别表示农场数目和小 K 记忆中的信息数目。
接下来 m m m 行:
如果每行的第一个数是 1 1 1,接下来有三个整数 a a a, b b b, c c c,表示农场 a a a 比农场 b b b 至少多种植了 c c c 个单位的作物;
如果每行的第一个数是 2 2 2,接下来有三个整数 a a a, b b b, c c c,表示农场 a a a 比农场 b b b 至多多种植了 c c c 个单位的作物;
如果每行的第一个数是 3 3 3,接下来有两个整数 a a a, b b b,表示农场 a a a 种植的的数量和 b b b 一样多。
如果存在某种情况与小 K 的记忆吻合,输出 Y e s Yes Yes,否则输出 N o No No。
3 3
3 1 2
1 1 3 1
2 2 3 2
Yes
对于 100 % 100\% 100% 的数据,保证 1 ≤ n , m , a , b , c ≤ 5 × 1 0 3 1 \le n,m,a,b,c \le 5 \times 10^3 1≤n,m,a,b,c≤5×103 。
#include
#define maxn 10005
using namespace std;
struct edge
{
int to, w;
};
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while (c > '9' || c < '0')
{
if (c == '-')
f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
vector<edge> g[maxn];
bool vis[maxn];
int n, m, dis[maxn];
bool spfa(int x)
{
vis[x] = true;
for (int i = 0; i < g[x].size(); i++)
{
int to = g[x][i].to;
int w = g[x][i].w;
if (dis[x] + w < dis[to])
{
dis[to] = dis[x] + w;
if (vis[to])
return false;
if (!spfa(to))
return false;
}
}
vis[x] = false;
return true;
}
int main()
{
n = read();
m = read();
for (int i = 1; i <= n; i++)
g[0].push_back({i, 0});
for (int i = 1, ins, a, b, c; i <= m; i++)
{
ins = read();
a = read();
b = read();
if (ins == 2)
{
c = read();
g[b].push_back({a, c});
}
else if (ins == 1)
{
c = read();
g[a].push_back({b, -c});
}
else
{
g[a].push_back({b, 0});
g[b].push_back({a, 0});
}
}
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
if (spfa(0))
cout << "Yes";
else
cout << "No";
return 0;
}
差分约束系统的步骤:
T H E E N D \mathcal{THE \ END} THE END