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:\) 从源点出发做一遍单源最短路

  1. 存在负环,原不等式一定无解
  2. 无负环,\(dis[i]\)就是一个解

2. 求每个变量的\(max\,or\,min\)

求最小值用最长路;求最大值用最短路

  1. \(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\),即 求上界最小值
  2. \(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\), 即 求下界最大值

糖果

题目

题目
Code

思路

题目的约束条件可以进行如下转化:

\[\begin{cases} A=B\leftrightarrow B\geq A且A\geq B & x=1\\ A < B \leftrightarrow B \geq A + 1 & x=2\\ A \geq B \leftrightarrow A \geq B & x=3\\ A > B \leftrightarrow A \geq B + 1 & x=4\\ A \leq B \leftrightarrow B \geq A & x=5\\ \end{cases} \]

绝对值:\(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

思路

Code

\(S_i\)表示\(1-i\)中被选出的数的个数,\(S_0=0\),即求\(S_{50001}\)的最小值

  1. \(S_i\geq S_{i-1},\,\,1\leq i\leq50001\)
  2. \(S_{i-1}\geq S_i-1\)
  3. \(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

思路

Code

最大值,最短路问题

限制条件:\(x_b-x_a\leq L\)\(x_b-x_a\geq D\)

  1. \(x_{i}\leq x_{i+1}, \,\, 1\leq i < b\)
  2. \(x_b\leq x_a+L\)
  3. \(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;
    }
}

雇佣收营员

还没做,嘿嘿

posted @ 2022-05-01 17:58  Sakana~  阅读(41)  评论(0编辑  收藏  举报