- 传送门 -
http://poj.org/problem?id=1716
Description
An integer interval [a,b], a < b, is a set of all consecutive integers beginning with a and ending with b.
Write a program that: finds the minimal number of elements in a set containing at least two different integers from each interval.
Input
The first line of the input contains the number of intervals n, 1 <= n <= 10000. Each of the following n lines contains two integers a, b separated by a single space, 0 <= a < b <= 10000. They are the beginning and the end of an interval.
Output
Output the minimal number of elements in a set containing at least two different integers from each interval.
Sample Input
4
3 6
2 4
0 2
4 7
Sample Output
4
Source
[Submit] [Status] [Discuss]
- 题意 -
有 n 个区间.
求一个集合, 使得每一个区间都至少有两个数在该集合中.
问集合中最少有几个数.
- 思路 -
有思想的差分约束.
差分约束的详解: http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html
首先设 (S[x]) 表示 (0 o x) 中有 (S[x]) 个数.
可知 :
对于给定区间 [a, b] 有:
(S[b] - S[a-1] >= 2) --- 1
对于全体有:
(S[x] - S[x-1] >= 0) --- 2
(S[x] - S[x-1] <= 1) --- 3
先谈谈我对差分约束的理解:
假设我们有一些关于 (D) 的不等式组, 要求(D[n] - D[0])的最大值
若要以不等式来建边, 先要保证不等式组(默认左边是变量, 右边是常数,例如上面的式子)的符号一致. (默认0是起点, n 是终点)
假设得到的式子形式如下:
(D[y] - D[x] <= m) (保证 <= 号, y 大于 x)
那我们就建一条从 (x) 指向 (y) 的权值为 (m) 的有向边.(谨记式子的表示: 指向的点 - 指出的点 = 边权)
要求(max(D[n] - D[0])), 所以看成是取了等号, 使边权尽量大.
假设找到了一条可行的路径:(0 o^{v1} a o^{v2} b o^{v3} n), 由上式可知:
(D[n]-D[b]=v3)
(D[b]-D[a]=v2)
(D[a]-D[0]=v1)
三式相加得:
(D[n]-D[0]=v1+v2+v3)
也就是说, 找到(0), (n) 两点间的路径, 也就找到了一个(D[n]-D[0]) 的值.
但我们知道, 一开始给出的是不等式组, 找出的答案满足这条路径表示的不等式, 却不一定满足其它的, 所以我们要找的是最短路, 最短路找到的解是最小的, 对于固定小于等于号的不等式来说, 一定满足不在最短路上的路径表示的式子.
(可以理解为, 对于非最短路, 我们求得的解能作为不等式左侧满足不等式, 那对于一个更小的解(最短路)当然也满足了)
综上, (max(D[n]-D[0]))即为最短路.
再看这道题, 它让我们求的是最小值, 那我们可以反过来, 已知(D[n]-D[0])一定是正数, 我们可以求负数(D[0]-D[n])的最大值, 大部分步骤和上面一样, 只是要从 (n) 找起, 找到 (0) 后再取相反数.
因为我们的式子形式为(D[x] - D[y] <= m) (保证 <= 号, y 大于 x)(可以把最开始的三个式子中的 1 化成小于等于看看是不是这样), 据此, 我们会建从大点连向小点的边, 所以要从 (n) 找起.
(对于 3 式来说确实是小点连向大点, 但是我们要求的是(D[0]-D[n]), 所以要保证选到的不等式加起来左边变成(D[0]-D[n]), 也就是 (n) 到 (0) 的一条边).
同样我们可以对不等式组固定大于等于号(左变量右常量)来解决问题.
大致上和小于号的情况是一样的, 只是要保证找到的解满足每一条路径(大于等于号的不等式), 我们要求的就变成了最长路,其它的就照上面分析就好了.
总的来说, 分析这种问题的关键在于分析每条边表示的不等式, 把路径上的不等式综合一下, 看左边(变量)得到的是什么.
对于本题来说, 上面提到的 n 应该是区间右端点最大值, 还要注意为了赋初值, 我们把给出的数都看大 1 , S[0] 就默认为 0 了.
细节见代码.
- 代码 -
大于号:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 2e4 + 5;
const int M = 4e4 + 5;
int HD[N], NXT[M], TO[M], V[M];
int VIS[N], DIS[N];
int n, m, r, sz;
queue<int>Q;
void add(int frm, int to, int val) {
V[++sz] = val; TO[sz] =to;
NXT[sz] = HD[frm]; HD[frm] = sz;
}
int spfa() {
memset(DIS, 128, sizeof (DIS));
DIS[0] = 0;
Q.push(0);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
VIS[u] = 0;
for (int i = HD[u]; i; i = NXT[i]) {
int v = TO[i];
if (DIS[v] < DIS[u] + V[i]) {
DIS[v] = DIS[u] + V[i];
if (!VIS[v]) Q.push(v);
}
}
}
return DIS[r];
} //找一条从 0 到 r 的最长路
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y + 1, 2);
r = max(y, r);
}
r++;
for (int i = 0; i < r; ++i) {
add(i, i + 1, 0);
add(i + 1, i, -1);
}
int ans = spfa();
printf("%d
", ans);
return 0;
}
小于号:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 2e4 + 5;
const int M = 4e4 + 5;
int HD[N], NXT[M], TO[M], V[M];
int VIS[N], DIS[N];
int n, m, r, sz;
queue<int>Q;
void add(int frm, int to, int val) {
V[++sz] = val; TO[sz] =to;
NXT[sz] = HD[frm]; HD[frm] = sz;
}
int spfa() {
memset(DIS, 127, sizeof (DIS));
DIS[r] = 0;
Q.push(r);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
VIS[u] = 0;
for (int i = HD[u]; i; i = NXT[i]) {
int v = TO[i];
if (DIS[v] > DIS[u] + V[i]) {
DIS[v] = DIS[u] + V[i];
if (!VIS[v]) Q.push(v);
}
}
}
return -DIS[0];
}找一条从 r 到 0 的最短路, 再取相反数
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
add(y + 1, x, -2);
r = max(y, r);
}
r++;
for (int i = 0; i < r; ++i) {
add(i + 1, i, 0);
add(i, i + 1, 1);
}
int ans = spfa();
printf("%d
", ans);
return 0;
}