差分约束系统 + spfa求最短路

首先先来个具体的题目来引入一下。

Intervals(UVA1723)

题面翻译

nnn 个区间,在区间 [ai,bi][a_i,b_i][ai,bi] 中至少取任意互不相同的 cic_ici 个整数。求在满足 nnn 个区间的情况下,至少要取多少个正整数。

输入格式

本题有多组数据

第一行的一个整数 TTT 表示数据个数,后面有一行空行。

对于每组数据:

第一行包含一个整数 n(1≤n≤50000)n(1\leq n\leq 50000)n(1n50000) 表示区间数。

以下 nnn 行描述区间。

输入的第 i+1i+1i+1 行包含三个整数 ai,bi,cia_i,b_i,c_iai,bi,ci,由空格分开。其中 0≤ai≤bi≤50000,1≤ci≤bi−ai+10\leq a_i\leq b_i\leq 50000,1\leq c_i\leq b_i-a_i+10aibi500001cibiai+1

输出格式

对于每组数据,输出一个对于 nnn 个区间 [ai,bi][a_i,b_i][ai,bi]
至少取 cic_ici 个不同整数的数的总个数。

在除了最后一组数据后输出空行。

样例

input:

1

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

output:

6

样例解释

可以取 3,4,5,8,9,103,4,5,8,9,103,4,5,8,9,10,为符合条件且取数个数最少的一组解。

题目模型的转换

我们建立一个新的数组,d[i]d[i]d[i] 表示 [0,i−1][0, i - 1][0,i1] 这个区间内一共被选中了多少个数字,我们用题目中给出来的样例来举例子。

5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1

通过上面的这些数据,我们就能知道 d[i]d[i]d[i] 需要满足以下这些条件。

{d[8]−d[3]≥3d[11]−d[8]≥3d[9]−d[6]≥1d[4]−d[1]≥1d[12]−d[10]≥1\left\{\begin{aligned} d[8] - d[3] \ge 3 \\ d[11]-d[8] \ge 3 \\ d[9] - d[6] \ge 1 \\ d[4] - d[1] \ge 1 \\ d[12] - d[10] \ge 1 \end{aligned}\right.d[8]d[3]3d[11]d[8]3d[9]d[6]1d[4]d[1]1d[12]d[10]1

然后我们再重新整理一下上面的这些不等式。

{d[8]≥d[3]+3d[11]≥d[8]+3d[9]≥d[6]+1d[4]≥d[1]+1d[12]≥d[10]+1\left\{\begin{aligned} d[8] \ge d[3] + 3 \\ d[11] \ge d[8] + 3 \\ d[9] \ge d[6] + 1 \\ d[4] \ge d[1] + 1 \\ d[12] \ge d[10] + 1 \end{aligned}\right.d[8]d[3]+3d[11]d[8]+3d[9]d[6]+1d[4]d[1]+1d[12]d[10]+1

除此之外,我们需要注意,因为 d 数组是一个前缀和,所以我们也需要维护好前缀和的合法性,也就是:∀i>0,d[i]≥d[i−1],d[i−1]≥d[i]−1\forall i>0,d[i] \ge d[i - 1],d[i - 1]\ge d[i] - 1i>0,d[i]d[i1],d[i1]d[i]1,这两个式子使得两个相邻的 d 元素,满足前缀和的要求,后面的要么等于前面的,要么比前面的大一。

那么我们只需要使得 d 数组 恰好 满足上面的种种条件,或者说使得 d 数组 恰好 满足上面的种种约束,我们就可以求出此题的答案(例如样例中 d[12] 就是最终结果,因为被选中的数字肯定小于 12)。

用 spfa 求出满足所有约束的 d 数组

我们先来看一下 spfa 的代码。

int spfa()
{
    memset(d, -0x3f3f3f3f, sizeof(d));
    memset(vis, 0, sizeof(vis));
    d[0] = 0, vis[0] = 1;
    queue<int> q;
    q.push(0);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = 0; i < e[u].size(); i++)
        {
            int v = e[u][i].first;
            int w = e[u][i].second;
            if (d[v] < d[u] + w) // 注意这里
            {
                d[v] = d[u] + w;
                if (!vis[v])
                {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    for (int i = 0; i <= 50000; i++)
        e[i].clear();
    return d[t];
}

这是一个 spfa 的代码,我们注意代码中出现注释的部分,spfa 会不停的寻找满足这个条件的边,找到了之后呢?我们就会修改 d[v] 的值,使得 d[v]≥d[u]+wd[v] \ge d[u] + wd[v]d[u]+w (大家有没有觉得这个式子特别的熟悉?),直到所有的边全部都恰好满足条件为止,那么我们为什么不能用上面 d 数组中元素两两之间的关系来代替边权呢?

所以此题就有了以下的这个代码。

#include <bits/stdc++.h>
#define mp(a, b) make_pair(a, b)

using namespace std;

const int maxn = 50005;
int dis[maxn], vis[maxn], n, t;

vector<pair<int, int>> e[maxn];

int spfa() // 正常的一个最长路的代码,注意初始化
{
    memset(dis, -0x3f3f3f3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[0] = 0, vis[0] = 1;
    queue<int> q;
    q.push(0);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = 0; i < e[u].size(); i++)
        {
            int v = e[u][i].first;
            int w = e[u][i].second;
            if (dis[v] < dis[u] + w)
            {
                dis[v] = dis[u] + w;
                if (!vis[v])
                {
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }
    for (int i = 0; i <= 50000; i++)
        e[i].clear();
    return dis[t];
}

int solve()
{
    cin >> n;
    t = 0;
    for (int i = 1; i <= n; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        e[a].push_back(mp(b + 1, c)); // 题目中给出的约束
        t = max(t, b + 1);            // t 是图中最大的点,所有被选中的数字全部都小于 t
    }
    for (int i = 1; i <= t; i++)
    {
        e[i - 1].push_back(mp(i, 0)); // 为了使得前缀和合法而添加的约束
        e[i].push_back(mp(i - 1, -1));
    }
    int ans = spfa();
    return ans;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cout << solve() << '\n';
        if (T) // 输出格式有要求,大家要注意
            cout << '\n';
    }
    return 0;
}









本人能力有限,如有不当之处敬请指教!祝大家新年快乐!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值