[学习笔记] 差分约束
一、差分约束
差分约束可以求解如下问题的一组解:
其中 \(1 \leq a_i, b_i \leq n\)。
我们发现这个很像求最短路的过程,即三角形不等式,所以我们从 \(a_i\) 向 \(b_i\) 连一条长度为 \(c_i\) 的边。
然后跑最短路。跑完后的 \(dis\) 数组就是一组解。
如果有负环就是无解。
因为 \(c_i\) 不一定非负,所以使用 SPFA。
要求解最小应该要反着建边。
Template:https://www.luogu.com.cn/problem/P5960
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
int n, m, cnt, head[5005], dis[5005], isin[5005], que[5005];
struct Edge {
int to, nxt, dis;
} ed[10005];
inline void add_edge (int u, int v, int w) {
cnt ++;
ed[cnt] = (Edge) {v, head[u], w};
head[u] = cnt;
return ;
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (m);
for (int i = 1;i <= m; ++ i) {
int u, v, w;
read (u), read (v), read (w);
add_edge (v, u, w);
}
for (int i = 1;i <= n; ++ i) add_edge (0, i, 0);
for (int i = 1;i <= n; ++ i) dis[i] = 1073741819;
dis[0] = 0;
isin[0] = 1;
queue < int > q;
while (!q.empty ()) q.pop ();
q.push (0);
while (!q.empty ()) {
int u = q.front (); q.pop ();
isin[u] = 0, que[u] ++;
if (que[u] > n + 1) {
printf ("NO\n");
return 0;
}
for (int i = head[u]; i ; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].dis;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!isin[v]) isin[v] = 1, q.push (v);
}
}
}
for (int i = 1;i <= n; ++ i) printf ("%d ", dis[i]);
printf ("\n");
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
二、例题
洛谷 P1993 小 K 的农场
https://www.luogu.com.cn/problem/P1993
设第 \(i\) 个农场种植了 \(x_i\) 个单位的作物。
条件一
农场 \(a\) 比农场 \(b\) 至少多种植了 \(c\) 个单位的作物。
- \(x_a - x_b \geq c (x_a - c \geq x_b)\)
转化为建图:
add_edge (a, b, -c);
条件二
农场 \(a\) 比农场 \(b\) 至多多种植了 \(c\) 个单位的作物。
- \(x_a - x_b \leq c\)
转化一下:\(x_b - x_a \geq -c(x_b + c \geq x_a)\)。
转化为建图:
add_edge (b, a, c);
条件三
农场 \(a\) 与农场 \(b\) 种植的作物数一样多。
那么就是 \(x_a + 0 \geq x_b, x_b + 0 \geq x_a\)。
转化为建图:
add_edge (a, b, 0);
add_edge (b, a, 0);
然后就是跑 SPFA 判负环了,这个题不用输出方案,非常好。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
int n, m, cnt, head[5005], dis[5005], isin[5005], que[5005];
struct Edge {
int to, nxt, dis;
} ed[15005];
inline void add_edge (int u, int v, int w) {
cnt ++;
ed[cnt] = (Edge) {v, head[u], w};
head[u] = cnt;
return ;
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (m);
for (int i = 1;i <= m; ++ i) {
int type;
read (type);
if (type == 1) {
int a, b, c;
read (a), read (b), read (c);
add_edge (a, b, -c);
}
else if (type == 2) {
int a, b, c;
read (a), read (b), read (c);
add_edge (b, a, c);
}
else {
int a, b;
read (a), read (b);
add_edge (a, b, 0), add_edge (b, a, 0);
}
}
for (int i = 1;i <= n; ++ i) add_edge (0, i, 0);
for (int i = 1;i <= n; ++ i) dis[i] = 1073741819;
dis[0] = 0;
isin[0] = 1;
queue < int > q;
while (!q.empty ()) q.pop ();
q.push (0);
while (!q.empty ()) {
int u = q.front (); q.pop ();
isin[u] = 0, que[u] ++;
if (que[u] > n + 1) {
printf ("No\n");
return 0;
}
for (int i = head[u]; i ; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].dis;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!isin[v]) isin[v] = 1, q.push (v);
}
}
}
printf ("Yes\n");
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
洛谷 P2474 [SCOI2008]天平
https://www.luogu.com.cn/problem/P2474
因为题目中只给出了大小关系,所以我们设:
-
\(mn_{i, j}\) 表示 \(w_i - w_j\) 的最小值。
-
\(mx_{i, j}\) 表示 \(w_i - w_j\) 的最大值。
其中 \(w_i\) 表示第 \(i\) 个砝码的重量。
初值
-
若第 \(i\) 行第 \(j\) 列为
+
,那么 \(mx_{i, j} = 2, mn_{i, j} = 1\)。 -
若第 \(i\) 行第 \(j\) 列为
-
,那么 \(mx_{i, j} = -1, mn_{i, j} = -2\)。 -
若第 \(i\) 行第 \(j\) 列为
=
,那么 \(mx_{i, j} = mn_{i, j} = 0\)。 -
若第 \(i\) 行第 \(j\) 列为
?
,那么 \(mx_{i, j} = 2, mn_{i, j} = -2\)。
特别的,当 \(i = j\) 时,按 =
处理。
正确性显然。
转移方程
显然 \(w_i - w_j \leq mx_{i, j}\),找一个中介点 \(k\):
还要满足 \(w_i - w_j \leq mx_{i, j}\),所以是取 min,mn 数组的转移同理。
因为 \(n\) 只有 \(50\),所以我们可以跑 Floyd。
统计答案
1. \(w_A + w_B > w_C + w_D\)
因为一定要满足,所以 \(mn_{A, C} > mx_{D, B}\)。
另一种:
因为一定要满足,所以 \(mn_{B, C} > mx_{D, A}\)。
2. \(w_A + w_B = w_C + w_D\)
因为一定要满足,所以 \(mn_{A, C} = mx_{A, C} = mn_{D, B} = mx_{D, B}\)。
另一种:
因为一定要满足,所以 \(mn_{B, C} = mx_{B, C} = mn_{D, A} = mx_{D, A}\)。
3. \(w_A + w_B < w_C + w_D\)
因为一定要满足,所以 \(mx_{A, C} < mn_{D, B}\)。
另一种:
因为一定要满足,所以 \(mx_{B, C} < mn_{D, A}\)。
代码
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
int n, A, B;
int mn[55][55], mx[55][55];
char s[55][55];
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (A), read (B);
for (int i = 1;i <= n; ++ i) scanf ("%s", s[i] + 1);
for (int i = 1;i <= n; ++ i) s[i][i] = '=';
for (int i = 1;i <= n; ++ i) {
for (int j = 1;j <= n; ++ j) {
if (s[i][j] == '+') mx[i][j] = 2, mn[i][j] = 1;
if (s[i][j] == '-') mx[i][j] = -1, mn[i][j] = -2;
if (s[i][j] == '=') mx[i][j] = mn[i][j] = 0;
if (s[i][j] == '?') mx[i][j] = 2, mn[i][j] = -2;
}
}
for (int k = 1;k <= n; ++ k) {
for (int i = 1;i <= n; ++ i) {
for (int j = 1;j <= n; ++ j) {
if (i != j && i != k && k != j) {
mx[i][j] = min (mx[i][j], mx[i][k] + mx[k][j]);
mn[i][j] = max (mn[i][j], mn[i][k] + mn[k][j]);
}
}
}
}
int ans1 = 0, ans2 = 0, ans3 = 0;
for (int i = 1;i <= n; ++ i) {
for (int j = i + 1;j <= n; ++ j) {
if (i ^ A) ; else continue;
if (j ^ A) ; else continue;
if (i ^ B) ; else continue;
if (j ^ B) ; else continue;
ans1 += (mn[A][i] > mx[j][B] || mn[B][i] > mx[j][A]);
ans2 += ((mn[A][i] == mx[A][i] && mn[j][B] == mx[j][B] && mn[A][i] == mn[j][B]) || ((mn[B][i] == mx[B][i] && mn[j][A] == mx[j][A] && mn[B][i] == mn[j][A])));
ans3 += (mx[A][i] < mn[j][B] || mx[B][i] < mn[j][A]);
}
}
printf ("%d %d %d\n", ans1, ans2, ans3);
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
洛谷 P4926 [1007] 倍杀测量者
https://www.luogu.com.cn/problem/P4926
转化一下问题,求最大的正实数 \(T\),使得以下不等式无解,若不存在合法的 \(T\) 就输出 \(-1\)。
设 \(s_x\) 表示第 \(x\) 个选手的分数。
Flag type 1
“如果 X 没有 \(k-T\) 倍杀选手 Y,X 就女装。”
Flag type 2
“如果 X 选手 \(k+T\) 倍杀选手 Y,Y 就女装。”
处理初值
直接转化为 \(s_C - s_0 \geq x, s_C + s_0 \leq x\) 即可。
代码
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
int n, s, t, cnt, head[1005], isin[1005], que[1005];
double dis[1005];
int o[1005], X[1005], Y[1005], k[1005];
int C[1005], val[1005];
struct Edge {
int to, nxt;
double dis;
} ed[10005];
inline void add_edge (int u, int v, double w) {
cnt ++;
ed[cnt] = (Edge) {v, head[u], w};
head[u] = cnt;
return ;
}
inline void cls () {
for (int i = 0;i <= n; ++ i) head[i] = 0;
cnt = 0;
}
inline bool SPFA () {
for (int i = 1;i <= n; ++ i) dis[i] = 1073741819.0;
for (int i = 1;i <= n; ++ i) isin[i] = 0;
for (int i = 0;i <= n; ++ i) que[i] = 0;
dis[0] = 0.0;
isin[0] = 1;
queue < int > q;
while (!q.empty ()) q.pop ();
q.push (0);
while (!q.empty ()) {
int u = q.front (); q.pop ();
isin[u] = 0, que[u] ++;
if (que[u] > n + 1) {
return false;
}
for (int i = head[u]; i ; i = ed[i].nxt) {
int v = ed[i].to;
double w = ed[i].dis;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!isin[v]) isin[v] = 1, q.push (v);
}
}
}
return true;
}
inline bool solve (double T) {
cls ();
for (int i = 1;i <= s; ++ i) {
if (o[i] == 1) {
double K = k[i];
K -= T;
if (K < 1e-10) K = -1919810;
else K = log2 (K);
add_edge (X[i], Y[i], -K);
}
else {
double K = k[i];
K += T;
K = log2 (K);
add_edge (Y[i], X[i], K);
}
}
for (int i = 1;i <= t; ++ i) {
double W = log2 (val[i]);
add_edge (C[i], 0, -W);
add_edge (0, C[i], W);
}
return !SPFA ();
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (s), read (t);
for (int i = 1;i <= s; ++ i) read (o[i]), read (X[i]), read (Y[i]), read (k[i]);
for (int i = 1;i <= s; ++ i) {
if (o[i] == 2) swap (X[i], Y[i]);
}
for (int i = 1;i <= t; ++ i) read (C[i]), read (val[i]);
if (!solve (0)) printf ("-1\n");
else {
double L = 0, R = 10;
while (R - L > 1e-5) {
double mid = (L + R) / 2.0;
if (solve (mid)) L = mid;
else R = mid;
}
if (solve (R)) printf ("%.8lf\n", R);
else printf ("%.8lf\n", L);
}
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/
洛谷 P5590 赛车游戏
https://www.luogu.com.cn/problem/P5590
设 \(d_i\) 表示 \(1\) 到 \(i\) 的最短路。
对于每一条有向边 \((u, v)\),要保证 \(1\) 到 \(n\) 的每一条路径长度相等,\((u, v)\) 的边权一定要是 \(d_v - d_u\)。
特别的,如果 \((u, v)\) 不在任何一条 \(1\) 到 \(n\) 的路径上,那么就可以随便标权值。
因为每一条边的边权在 \([1, 9]\) 之间,所以:
-
\(d_v - d_u \geq 1\) -> \(d_v - 1 \geq + d_u\)
-
\(d_v - d_u \leq 9\) -> \(d_u + 9 \geq + d_v\)
然后建图,跑差分约束,求出 \(d\),若有负环就无解。
若 \(1\) 不能到达 \(n\) 也是无解!
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
Z x = 0, f = 0;
char c = getchar ();
for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
tmp = !f ? x : -x;
}
int n, m, cnt, head[1005], dis[1005], isin[1005], que[1005];
struct Edge {
int to, nxt, dis;
} ed[5005];
inline void add_edge (int u, int v, int w) {
cnt ++;
ed[cnt] = (Edge) {v, head[u], w};
head[u] = cnt;
return ;
}
int fir[2005], sec[2005], flag[2005], ok;
vector < int > gr[2005], fgr[2005];
int fst[1005], sct[1005];
inline void dfs (int u) {
if (u == n) {
fst[n] = 1;
ok = 1;
return ;
}
if (fst[u]) return ;
fst[u] = 1;
for (int v : gr[u]) {
dfs (v);
}
}
inline void dfs2 (int u) {
if (u == 1) {
sct[1] = 1;
return ;
}
if (sct[u]) return ;
sct[u] = 1;
for (int v : fgr[u]) {
dfs2 (v);
}
}
bool Memory_Ends;
signed main () {
fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
read (n), read (m);
for (int i = 1;i <= m; ++ i) {
read (fir[i]), read (sec[i]);
gr[fir[i]].push_back (sec[i]);
fgr[sec[i]].push_back (fir[i]);
}
dfs (1);
dfs2 (n);
if (!ok) {
printf ("-1\n");
return 0;
}
for (int i = 1;i <= m; ++ i) {
if (fst[fir[i]] && sct[sec[i]]) flag[i] = 1;
}
for (int i = 1;i <= m; ++ i) {
if (flag[i]) {
add_edge (sec[i], fir[i], -1);
add_edge (fir[i], sec[i], 9);
}
}
for (int i = 1;i <= n; ++ i) add_edge (0, i, 0);
for (int i = 1;i <= n; ++ i) dis[i] = 1073741819;
dis[0] = 0;
isin[0] = 1;
queue < int > q;
while (!q.empty ()) q.pop ();
q.push (0);
while (!q.empty ()) {
int u = q.front (); q.pop ();
isin[u] = 0, que[u] ++;
if (que[u] > n + 1) {
printf ("-1\n");
return 0;
}
for (int i = head[u]; i ; i = ed[i].nxt) {
int v = ed[i].to, w = ed[i].dis;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!isin[v]) isin[v] = 1, q.push (v);
}
}
}
printf ("%d %d\n", n, m);
for (int i = 1;i <= m; ++ i) {
if (flag[i]) printf ("%d %d %d\n", fir[i], sec[i], dis[sec[i]] - dis[fir[i]]);
else printf ("%d %d %d\n", fir[i], sec[i], 1);
}
fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/