一、定义
现有 n n n 个变量 a 1 , a 2 , . . . , a n a_1, a_2, ... ,a_n a1,a2,...,an,给出 m m m 个不等式,第 i i i 个形如 a i − a j ≤ k a_i - a_j \leq k ai−aj≤k( k k k 为常数, 1 ≤ i , j ≤ m 1 \leq i, j \leq m 1≤i,j≤m ),请构造出一组符合条件的数列 a a a。
二、解法
要求构造 a a a,直接暴力不好搞,时间复杂度也很高,考虑转化问题。
令 d i s i dis_i disi 为超级原点到节点 i i i 的最短路径长,如果一个节点 u u u 可以通过一条权值为 w w w 的边到达节点 v v v 的话,根据最短路径的性质,则一定满足 d i s v ≤ d i s u + w dis_v \leq dis_u + w disv≤disu+w。化简可得 d i s v − d i s u ≤ w dis_v - dis_u \leq w disv−disu≤w。与上述差分约束的不等式及其相似。所以当我们拿到一个不等式 a i − a j ≤ k a_i - a_j \leq k ai−aj≤k 时,可以在节点 j j j 和节点 i i i 间连接一条权值为 k k k 的有向边,再建立一个超级源点,求出到任意节点 i i i 的最短路径,便得到了一组答案。
当然,有时候题目中有隐藏的限制条件,这就因题而异了。
什么?你问怎么考虑无解的情况?还有无解的情况?!
是的,怎么考虑无解呢?
既然我们已经将问题转化成求解最短路了,所以只需要搞清楚最短路什么时候无解就行了。最短路无解的情况只有一种:图中存在 负环。
因此,Dijkstra 死掉了。所以处理差分约束类问题时,我们一般选择 SPFA 算法。设节点 i i i 入队次数为 n u m i num_i numi,则当 n u m i ≥ n num_i \geq n numi≥n 时,图中存在负环。
补充:常见的不等式转化方式如下:
若 a − b = k a - b = k a−b=k,转化为 a − b ≤ k a - b \leq k a−b≤k 且 a − b ≥ k a - b \geq k a−b≥k;
若 a − b > k a - b > k a−b>k,转化为 a − b ≥ k + 1 a - b \geq k + 1 a−b≥k+1;
若 a − b < k a - b < k a−b<k,转为为 a − b ≤ k − 1 a - b \leq k - 1 a−b≤k−1;
三、例题
1、差分约束系统
分析:
裸题,直接套用上述结论即可。
顺便挂个模板:
#include<bits/stdc++.h>
using namespace std;
#define SF scanf
#define PF printf
struct Edge {
int to, next, w;
}edge[50005];
struct node {
int dis, id;
};
int n, m, cnt, head[5005], num[5005], dis[5005];
bool vis[5005];
void add(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].w = w;
head[u] = cnt;
}
void SPFA(int x) {
for(int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f;
dis[x] = 0;
queue<node> q;
q.push((node){0, x}), vis[x] = 1;
while(!q.empty()) {
node tmp = q.front();
q.pop();
vis[tmp.id] = 0;
num[tmp.id]++;
if(num[tmp.id] == n) {
PF("NO");
exit(0);
}
for(int i = head[tmp.id]; i; i = edge[i].next) {
int to = edge[i].to;
if(dis[to] > dis[tmp.id] + edge[i].w) {
dis[to] = dis[tmp.id] + edge[i].w;
if(!vis[to]) {
q.push((node){dis[to], to});
vis[to] = 1;
}
}
}
}
}
int main() {
SF("%d%d", &n, &m);
for(int i = 1; i <= n; i++) add(0, i, 0);
for(int i = 1; i <= m; i++) {
int u, v, w;
SF("%d%d%d", &u, &v, &w);
add(v, u, w);
}
SPFA(0);
for(int i = 1; i <= n; i++) PF("%d ", dis[i]);
return 0;
}
2、天平
分析:
显然输入给了我们很多不等式。
一共有 n n n 个砝码,设第 i i i 个砝码重 w i w_i wi( 1 ≤ w i ≤ 3 1 \leq w_i \leq 3 1≤wi≤3 )克。
现在已经给出了两个砝码 A A A 和 B B B。把它们放在了天平的一边,还需要另外两个砝码 C C C 和 D D D,放在天平的另一边。
因为 4 ≤ n ≤ 50 4 \leq n \leq 50 4≤n≤50,范围很小,可以直接对 C C C 和 D D D进行枚举。
要分别求出 w A + w B > w C + w D w_A + w_B > w_C + w_D wA+wB>wC+wD, w A + w B = w C + w D w_A + w_B = w_C + w_D wA+wB=wC+wD 和 w A + w B < w C + w D w_A + w_B < w_C + w_D wA+wB<wC+wD的方案数,设其分别为 c 1 c1 c1、 c 2 c2 c2、 c 3 c3 c3。
关键在于怎样求出每一个 w i w_i wi,但这明显有点难,而且答案可能不唯一。
考虑对要求的三个不等式进行转化。
若要满足 w A + w B > w C + w D w_A + w_B > w_C + w_D wA+wB>wC+wD,即满足 w A − w C > w D − w B w_A - w_C > w_D - w_B wA−wC>wD−wB 或 w A − w D > w C − w B w_A - w_D > w_C - w_B wA−wD>wC−wB 即可。
如果 min { w A − w C } > max { w D − w B } \min\{w_A - w_C\} > \max\{w_D - w_B\} min{wA−wC}>max{wD−wB} 或 min { w A − w D } > max { w C − w B } \min\{w_A - w_D\} > \max\{w_C - w_B\} min{wA−wD}>max{wC−wB},则一定满足第一个不等式。累计 c 1 c1 c1。
其余两个不等式转化方式同上。
至此,问题转化为:对于任意的 i i i 和 j j j,设其满足条件的最小差值为 m i n n i , j minn_{i,j} minni,j,满足条件的最大差值为 m a x n i , j maxn_{i,j} maxni,j,求解出所有的 m i n n minn minn 和 m a x n maxn maxn 即可。
Code:
#include<bits/stdc++.h>
using namespace std;
#define SF scanf
#define PF printf
int Min[55][55], Max[55][55], n;
void floyd() {
for(int k = 1; k <= n; k++) {
for(int i = 1; i <= n; i++) {
if(i == k) continue;
for(int j = 1; j <= n; j++) {
if(j == k || i == j) continue;
Min[i][j] = max(Min[i][j], Min[i][k] + Min[k][j]); //求最小跑最长
Max[i][j] = min(Max[i][j], Max[i][k] + Max[k][j]); //求最大跑最短
}
}
}
}
int main() {
int A, B;
SF("%d%d%d", &n, &A, &B);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
char k;
cin >> k;
if(k == '+') Max[i][j] = 2, Min[i][j] = 1;
else if(k == '=' || i == j) Max[i][j] = Min[i][j] = 0;
else if(k == '-') Max[i][j] = -1, Min[i][j] = -2;
else Max[i][j] = 2, Min[i][j] = -2;
}
}
floyd(); //数据很小,保证有解,可以用 floyd
int c1 = 0, c2 = 0, c3 = 0;
for(int i = 1; i <= n; i++) {
if(i == A || i == B) continue;
for(int j = i + 1; j <= n; j++) {
if(j == A || j == B) continue;
if(Min[A][i] > Max[j][B] || Min[A][j] > Max[i][B]) c1++;
if((Max[A][i] == Min[A][i] && Min[A][i] == Min[j][B] && Min[j][B] == Max[j][B]) ||
(Max[A][j] == Min[A][j] && Min[A][j] == Min[i][B] && Min[i][B] == Max[i][B])) c2++;
if(Max[A][i] < Min[j][B] || Max[A][j] < Min[i][B]) c3++;
}
}
PF("%d %d %d", c1, c2, c3);
return 0;
}
3、Intervals
分析:
设前 i i i 个数中选了 d i d_i di 个数。即要使每个 d i d_i di 尽可能小。
对于第 i i i 个条件: l i l_i li 到 r i r_i ri 中至少选择 w i w_i wi 个数。
即给出了 d r i − d l i − 1 ≥ w i d_{r_i} - d_{l_i - 1} \geq w_i dri−dli−1≥wi 这个限制条件。
转化成 d l i − 1 − d r i ≤ − w i d_{l_i - 1} - d_{r_i} \leq -w_i dli−1−dri≤−wi。就可以在 l i − 1 l_i - 1 li−1 和 r i r_i ri 之间连接权值为 − w i -w_i −wi 的有向边。
注意,这个题有隐藏条件!
你一定需要满足的条件是: 0 ≤ d i + 1 − d i ≤ 1 0 \leq d_{i + 1} - d_i \leq 1 0≤di+1−di≤1。
再根据这个不等式建边就行了。
Code:
#include<bits/stdc++.h>
using namespace std;
#define SF scanf
#define PF printf
struct Edge {
int to, next, w;
}edge[200005];
int head[50005], cnt, dis[50005], n, s;
bool vis[50005];
void add(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].w = w;
head[u] = cnt;
}
void SPFA(int x) {
queue<int> q;
q.push(x);
for(int i = 1; i <= s; i++) dis[i] = 0x3f3f3f3f;
vis[x] = 1, dis[x] = 0;
while(!q.empty()) {
int tmp = q.front();
q.pop();
vis[tmp] = 0;
for(int i = head[tmp]; i; i = edge[i].next) {
int to = edge[i].to;
if(dis[to] > dis[tmp] + edge[i].w) {
dis[to] = dis[tmp] + edge[i].w;
if(!vis[to]) {
vis[to] = 1;
q.push(to);
}
}
}
}
}
int main() {
SF("%d", &n);
for(int i = 1; i <= n; i++) {
int u, v, w;
SF("%d%d%d", &u, &v, &w);
u++, v++;
s = max(s, v);
add(v, u - 1, -w);
}
for(int i = 0; i < s; i++) add(i + 1, i, 0), add(i, i + 1, 1);
SPFA(s);
PF("%d", -dis[0]);
return 0;
}
4、矩阵游戏
分析:
调整法是个不错的方法。
首先我们可以先令第 n n n 行和第 m m m 列的元素都为 0 ,这样我们就能够得到一组答案 a a a,虽然此答案可能不合法,但我们可以在其基础上进行调整。
我们设一个变量 c c c ,易证在任意一行 i i i 中,将第 j j j 个元素 a i , j a_{i,j} ai,j 变为 a i , j + ( − 1 ) j ∗ c a_{i,j} + (-1) ^ j * c ai,j+(−1)j∗c,则该答案依旧合法。
再设一个变量 g g g ,易证在任意一列 r r r 中,将第 l l l 个元素 a l , r a_{l,r} al,r 变为 a l , r − ( − 1 ) l ∗ g a_{l,r} - (-1)^l * g al,r−(−1)l∗g,则该答案依旧合法。
这样对于任意一个元素 a i , j a_{i,j} ai,j,就将它变为了 a i , j + c − g a_{i,j} + c - g ai,j+c−g 或 a i , j − c + g a_{i,j} - c + g ai,j−c+g。
所以问题就转化成了:求解出各个行的 c c c 和各个列的 g g g 变量就可以了。
对其进行差分约束即可。
Code:
#include<bits/stdc++.h>
using namespace std;
#define SF scanf
#define PF printf
#define int long long
struct Edge {
int to, next, w;
}edge[500005];
int a[305][305], b[305][305], head[605], cnt, n, m, dis[605], num[605];
bool vis[100005];
void add(int u, int v, int w) {
edge[++cnt].to = v;
edge[cnt].next = head[u];
edge[cnt].w = w;
head[u] = cnt;
}
bool SPFA(int x) {
queue<int> q;
q.push(x);
for(int i = 1; i <= n + m; i++) dis[i] = 0x3f3f3f3f;
dis[x] = 0, vis[x] = 1;
while(!q.empty()) {
int tmp = q.front();
q.pop();
vis[tmp] = 0;
for(int i = head[tmp]; i; i = edge[i].next) {
int to = edge[i].to;
if(dis[to] > dis[tmp] + edge[i].w) {
dis[to] = dis[tmp] + edge[i].w;
if(++num[to] >= n + m) return false;
if(!vis[to]) {
vis[to] = 1;
q.push(to);
}
}
}
}
return true;
}
signed main() {
freopen("matrix.in", "r", stdin);
freopen("matrix.out", "w", stdout);
int t;
SF("%lld", &t);
while(t--) {
cnt = 0;
memset(head, 0, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
SF("%lld%lld", &n, &m);
for(int i = 1; i < n; i++) {
for(int j = 1; j < m; j++) SF("%lld", &b[i][j]);
}
for(int i = n - 1; i; i--) {
for(int j = m - 1; j; j--) a[i][j] = b[i][j] - a[i + 1][j] - a[i][j + 1] - a[i + 1][j + 1];
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if((i + j) & 1) {
// 0 <= aij + ci - gj <= 1e6
add(i, j + n, a[i][j]);
add(j + n, i, 1e6 - a[i][j]);
}
else {
// 0 <= aij - ci + gj <= 1e6
add(j + n, i, a[i][j]);
add(i, j + n, 1e6 - a[i][j]);
}
}
}
bool flag = SPFA(1);
if(!flag) {
PF("NO\n");
continue;
}
PF("YES\n");
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
if((i + j) & 1) PF("%lld ", a[i][j] + dis[i] - dis[j + n]);
else PF("%lld ", a[i][j] - dis[i] + dis[j + n]);
}
PF("\n");
}
}
return 0;
}