差分约束系统 + spfa求最短路
首先先来个具体的题目来引入一下。
Intervals(UVA1723)
题面翻译
有 n n n 个区间,在区间 [ a i , b i ] [a_i,b_i] [ai,bi] 中至少取任意互不相同的 c i c_i ci 个整数。求在满足 n n n 个区间的情况下,至少要取多少个正整数。
输入格式
本题有多组数据。
第一行的一个整数 T T T 表示数据个数,后面有一行空行。
对于每组数据:
第一行包含一个整数 n ( 1 ≤ n ≤ 50000 ) n(1\leq n\leq 50000) n(1≤n≤50000) 表示区间数。
以下 n n n 行描述区间。
输入的第 i + 1 i+1 i+1 行包含三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci,由空格分开。其中 0 ≤ a i ≤ b i ≤ 50000 , 1 ≤ c i ≤ b i − a i + 1 0\leq a_i\leq b_i\leq 50000,1\leq c_i\leq b_i-a_i+1 0≤ai≤bi≤50000,1≤ci≤bi−ai+1。
输出格式
对于每组数据,输出一个对于
n
n
n 个区间
[
a
i
,
b
i
]
[a_i,b_i]
[ai,bi]
至少取
c
i
c_i
ci 个不同整数的数的总个数。
在除了最后一组数据后输出空行。
样例
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 , 10 3,4,5,8,9,10 3,4,5,8,9,10,为符合条件且取数个数最少的一组解。
题目模型的转换
我们建立一个新的数组, d [ i ] d[i] d[i] 表示 [ 0 , i − 1 ] [0, i - 1] [0,i−1] 这个区间内一共被选中了多少个数字,我们用题目中给出来的样例来举例子。
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 ] ≥ 3 d [ 11 ] − d [ 8 ] ≥ 3 d [ 9 ] − d [ 6 ] ≥ 1 d [ 4 ] − d [ 1 ] ≥ 1 d [ 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 ] + 3 d [ 11 ] ≥ d [ 8 ] + 3 d [ 9 ] ≥ d [ 6 ] + 1 d [ 4 ] ≥ d [ 1 ] + 1 d [ 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] - 1 ∀i>0,d[i]≥d[i−1],d[i−1]≥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 ] + w d[v] \ge d[u] + w d[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;
}
本人能力有限,如有不当之处敬请指教!祝大家新年快乐!