[题解] poj 1716 Integer Intervals (差分约束+spfa)

- 传送门 -

 http://poj.org/problem?id=1716

#Integer Intervals | **Time Limit:** 1000MS |   | **Memory Limit:** 10000K | | **Total Submissions:** 15086 |   | **Accepted:** 6375 |

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

CEOI 1997


[Submit]   [Status]   [Discuss]

- 题意 -

 有 n 个区间.
 求一个集合, 使得每一个区间都至少有两个数在该集合中.
 问集合中最少有几个数.
 

- 思路 -

 有思想的差分约束.
 差分约束的详解: http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html
 首先设 \(S[x]\) 表示 \(0\to 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\to^{v1} a\to^{v2} b\to^{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\n", 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\n", ans);
	return 0;
}
posted @ 2017-08-20 14:17  lstttt  阅读(271)  评论(0编辑  收藏  举报