浅析区间问题
一、区间概述
1.1 区间的定义
区间可以看作在数轴上的一条线段。“在初等代数,传统上区间指一个集,包含在某两个特定实数之间的所有实数,亦可能包含该两个实数(或其中之一)。区间表示法是表示一个变数在某个区间内的方式。通用的区间表示法中,圆括号表示 ‘排除’,方括号表示 ‘包括’。”1
为了方便处理,我们这里如下形式化地定义一个区间:
类似的,可以定义
所有的z可被称为区间[x, y]中的点,区间[x, y]称为一条线段。
在下文中,如无特殊说明,均采用这种定义方式。
1.2 区间的基本性质
- 区间[x,y]的长度L([x,y])定义为区间集合的基数,即card([x, y])。不难发现,
L([x,y])=card([x,y])=y−x+1 。 - 区间的交、并、补,均符合集合的定义。特别地,如果区间X = [a, b], Y = [c, d], 有
X∩Y=∅ ,称X和Y不相交,否则称X和Y 相交。如果a≤c ,则有
- 如果区间X = [a, b], Y = [c, d]有
X⊆Y ,则称X是Y的子区间。且
- 定义权值函数为
W:N∗→R ,W(x)表示x这一点的权值;定义区间的权值F(X)
为了叙述方便,对于一个有权值的区间[a, b],我们通常表示为{W(a), W(a+1)…W(b)};在算法描述中,为了方便,我们可以用一个数组表示区间,数组中记录的元素为该位置上元素的权值。
二、区间覆盖问题
我们要研究的第一个区间问题是区间覆盖问题。
对于n和n个区间[ai,bi](i∈[1,n]) ,求
L(⋃i∈[1,n][ai,bi])
其中,ai,bi∈[1,m]
2.1 差分实现的O(n+m)解法
为了解决这个问题,我们先证明一些有用的定理。
前缀定义为区间前若干个数,通常对于区间[1, m], 他的k前缀为[1, k];前缀和定义为区间前若干个数的权值和,
Get-P(i, a, b, P)
1. P[a-1] = 0
2. for j = a to b
3. P[j] = P[j-1] + W(j)
4. // 这里用P[j]表示P(j, X)
差分是前缀和的逆运算,即给定前缀和构成的区间
Get-D(P[a..b], D)
1. P[a-1] = 0
2. for i = a to b
3. D[i] = P[i] - P[i-1]
4. // D[a..b]表示D(x)为所求,称D为差分数组
5. return D
更多情况下,我们不需要通过前缀和来计算差分,而是对差分数组求前缀和。以下定理将说明,利用差分数组的前缀和,我们可以解决区间覆盖问题:
定理2.1.1 在区间[1, m]中,存在某一子区间X = [a, b]。若将该区间的每一个点的权值加上k,则其差分数组有且只有两项发生变化:D[a]+=k, D[b+1]-=k
证明:由于上面求差分算法的正确性,我们不难发现,对于
定理2.1.2 定理2.1.1的逆定理,差分数组中D[a]+=k, D[b+1]-=k,则子区间X = [a, b]上的每一个点的权值加上k
证明:类似地,利用求前缀和算法的正确性,不难证明此定理。
因此,我们可以给出如下O(n+m)的算法来解决问题:
Get-Range-Cover(a, b, n)
1. D = [1..m], D[i] = 0
2. for i = 1 to n
3. D[a[i]] += 1
4. D[b[i]+1] -= 1
5. // 以上求出了差分数组
6. P = [1..m], P[0] = 0
7. for i = 1 to m
8. P[i] = P[i-1] + D[i]
9. // 获得前缀和
10.cnt = 0
11.for i = 1 to m
12. if P[i] > 0 then
13. cnt++
14.return cnt
该算法的复杂度是显然的。我们用下面的定理说明算法的正确性:
定理2.1.3 Get-Range-Cover算法是正确的
证明:定理2.1.2指出,在算法2-4行的计算中,使得P[i]被加一当且仅当其在一个区间内,即i被覆盖。因此既不会重复计算,也不会遗漏。
这个算法是相当高效的。如果采用暴力法,时间复杂度为O(mn)。差分对于算法的作用不言而喻。
2.2 广义差分的O(nlgn)解法
这个问题的一个简单变形是m变的非常大,这就使得O(n+m)的算法毫无用武之地。严格地说,O(n+m)的算法并不能算是多项式算法,因为决定算法效率的除了输入规模n还有与输入规模无关的量m,而m可以高达n的指数级。因此,我们急需一种真正的多项式算法解决这个问题。事实上,我们只需要对差分算法做一些简单的修改。如下定理将帮助我们更好的理解下面的算法。
注意:广义差分是笔者的称呼,并不是神犇们的使用惯例。
定理2.2.1 问题中的a,b数组分别从小到大排序,则有
证明:首先,a,b数组未排序时,
然后用反证法,假设对于已排好序的数组,
b_i-----a_i-----b_j-----a_j
b_i-----b_j-----a_i-----a_j
b_j-----b_i-----a_i-----a_j
b_i-----a_i-----a_j-----b_j
b_i-----b_j-----a_j-----a_i
b_j-----b_i-----a_j-----a_i
b_i-----a_j-----a_i-----b_j
b_i-----a_j-----b_j-----a_i
b_j-----a_j-----b_i-----a_i
a_j-----b_i-----a_i-----b_j
a_j-----b_i-----b_j-----a_i
a_j-----b_j-----b_i-----a_i
以上已经穷举出所有可能的
定理2.2.2 记原所有区间的并为
证明:由于集合并的性质,
对于X = [a, b], Y = [c, d],容易枚举两者所有的情况,用穷举法发现原式成立,故原命题得证。
根据以上两个定理,我们给出一个运行在O(nlgn)的算法:
New-Range-Cover(a, b, n)
1. Ascending sort a, b with Quick Sort Algorithm
2. ln = 0, ans = 0
3. // ln 记录 已经计算到的最右边
4. for i = 1 to n
5. if b[i] <= ln then
6. continue
7. // 此区间已经全部被计算过,跳过
8. if a[i] <= ln then
9. ans += r[i] - ln
10. ln = r[i] // 区间被部分计算过,更新
11. else
12. ln = r[i]
13. ans += r[i] - l[i]
14. // 区间未被计算过,更新并计算
15. return ans
此算法的复杂度是显然的,为排序的复杂度O(nlgn)加上后续计算O(n),总共为O(nlgn)。如果采用基数排序,可以取得更好的效果。
未完待续,慢慢填坑
- 引自wiki ↩