AcWing 算法提高课 差分约束专题
差分约束
原理
1. 求不等式组的可行解
求形如 \(x_i\leq x_j+c_k\)(两自变量,一常量)
该问题转化为,求从\(j\) 走到\(i\) 长度为\(c_k\)的一条边,的最短路问题
必须满足从源点出发能够走到所有的边
\(Step1:\) 讲不等式 \(x_i\leq x_j+c_k\)转化为从\(x_j\) 走到\(x_i\) 长度为\(c_k\)的一条边
\(Step2:\) 建立虚拟源点,使得该点出发可以到达所有边
\(Step3:\) 从源点出发做一遍单源最短路
- 存在负环,原不等式一定无解
- 无负环,\(dis[i]\)就是一个解
2. 求每个变量的\(max\,or\,min\)
求最小值用最长路;求最大值用最短路
- 求\(max:\) 转化 \(x_i\leq c:\) 建立超级源点 0,然后建立\(0\rightarrow i\),长度为 \(c\) 的边,有不等式链 \(x_i\leq x_j+c_1\leq x_k+c_2+c_1\leq …\leq x_0+c_1+c_2+...+c_m\),即 求上界最小值
- 求\(min:\) 转化 \(x_i\geq c:\) 建立超级源点 0,然后建立\(0\rightarrow i\),长度为 \(c\) 的边,有不等式链 \(x_i≥x_j+c_1≥x_k+c_2+c_1≥⋅⋅⋅≥x_0+c_1+c_2+⋅⋅⋅+c_m\), 即 求下界最大值
糖果
题目
思路
题目的约束条件可以进行如下转化:
绝对值:\(x\geq1\rightarrow x\geq x_0+1\)
队列过不了,得用栈优化
Code
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
const int N = 1e5 + 5, M = 3e5 + 5;//不是很懂为什么是这么大
int n, k;
int h[N], e[M], ne[M], w[M], idx;
bool vis[N];
int cnt[N];
ll dis[N];
stack <int> q;
void add (int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa () {
memset (dis, -0x3f, sizeof dis);
dis[0] = 0;
q.push (0), vis[0] = true; //和传统不一样
while (!q.empty()) {
int t = q.top();
q.pop();
vis[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
//最小值最大
if (dis[j] < dis[t] + w[i]) {
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n + 1) //注意是n+1
return false;
if (!vis[j])
q.push (j), vis[j] = true;
}
}
}
return true;
}
int main () {
IOS;
memset (h, -1, sizeof h);
cin >> n >> k;
while (k --) {
int op, a, b;
cin >> op >> a >> b;
if (op == 1)
add (a, b, 0), add (b, a, 0);
else if (op == 2)
add (a, b, 1);
else if (op == 3)
add (b, a, 0);
else if (op == 4)
add (b, a, 1);
else
add (a, b, 0);
}
//造虚拟源点
for (int i = 1; i <= n; i ++)
add (0, i, 1);
if (!spfa())
cout << -1 << endl;
else {
long long ans = 0;
for (int i = 1; i <= n; i ++)
ans += dis[i];
cout << ans << endl;
}
}
//最大值最小
区间
题目
给定 n 个区间 [ai,bi] 和 n 个整数 ci。
你需要构造一个整数集合 Z,使得 ∀i∈[1,n],Z 中满足 \(a_i≤x≤b_i\) 的整数 x 不少于 ci 个。
求这样的整数集合 Z 最少包含多少个数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含三个整数 ai,bi,ci。
输出格式
输出一个整数表示结果。
数据范围
\(1≤n≤50000,\)
\(0≤ai,bi≤50000,\)
\(1≤ci≤bi−ai+1\)
输入样例:
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
输出样例:
6
思路
\(S_i\)表示\(1-i\)中被选出的数的个数,\(S_0=0\),即求\(S_{50001}\)的最小值
- \(S_i\geq S_{i-1},\,\,1\leq i\leq50001\)
- \(S_{i-1}\geq S_i-1\)
- \(S_b-S_{a-1}\geq c\rightarrow S_b\geq S_{a-1}+c\)
注:a ++, b ++; // 避免0号位出现歧义
Code
//最小值 最长路
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5, M = N * 3;
int n;
int h[N], e[M], ne[M], w[M], idx;
queue <int> q;
bool vis[N];
int dis[N], cnt[N];
void add (int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
void spfa () {
memset (dis, -0x3f, sizeof h);
q.push (0), dis[0] = 0, vis[0] = true;
while (!q.empty()) {
int t = q.front();
q.pop();
vis[t]= false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dis[j] < dis[t] + w[i]) {
dis[j] = dis[t] + w[i];
cnt[j] = cnt[j] + 1;
// if (cnt[j] >= n + 1)
// return false;
if (!vis[j])
q.push (j), vis[j] = true;
}
}
}
//return true;
}
int main () {
memset (h, -1, sizeof h);
cin >> n;
dis[1] = 0;
for (int i = 1; i < N; i ++) { //注意是到N
add (i - 1, i, 0);
add (i, i - 1, -1);
}
for (int i = 1; i <= n; i ++) {
int a, b, c;
cin >> a >> b >> c;
a ++, b ++; //避免0号位出现歧义
add (a - 1, b, c);
}
spfa ();
cout << dis[50001] << endl;
}
排队布局
题目
https://www.luogu.com.cn/problem/P4878
思路
最大值,最短路问题
限制条件:\(x_b-x_a\leq L\) 和 \(x_b-x_a\geq D\)
- \(x_{i}\leq x_{i+1}, \,\, 1\leq i < b\)
- \(x_b\leq x_a+L\)
- \(x_a\leq x_b-D\)
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1005, M = N + 2e4;
int n, L, D;
int dis[N], cnt[N];
bool vis[N];
int h[N], e[M], ne[M], w[M], idx;
void add (int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa (int x) {
queue <int> q;
memset (dis, 0x3f, sizeof dis);
memset (cnt, 0, sizeof cnt);
memset (vis, false, sizeof vis);
for (int i = 1; i <= x; i ++) //x
q.push (i), vis[i] = true, dis[i] = 0;
while (!q.empty()) {
int t = q.front();
q.pop();
vis[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dis[j] > dis[t] +w[i]) {
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n)
return false;
if (!vis[j])
q.push (j), vis[j] = true;
}
}
}
return true;
}
int main () {
memset (h, -1, sizeof h);
cin >> n >> L >> D;
for (int i = 1; i < n; i ++)
add (i + 1, i, 0);
while (L --) {
int a, b, c;
cin >> a >> b >> c;
if (a > b)
swap (a, b);
add (a, b, c); //注意最短路的符号是相反的
}
while (D --) {
int a, b, c;
cin >> a >> b >> c;
if (a > b)
swap (a, b);
add (b, a, -c);
}
if (!spfa(n))
puts("-1");
else {
spfa(1);
if (dis[n] == 0x3f3f3f3f )
puts("-2");
else
cout << dis[n] << endl;
}
}
雇佣收营员
还没做,嘿嘿