• 浅析区间问题


    一、区间概述

    1.1 区间的定义

    区间可以看作在数轴上的一条线段。“在初等代数,传统上区间指一个集,包含在某两个特定实数之间的所有实数,亦可能包含该两个实数(或其中之一)。区间表示法是表示一个变数在某个区间内的方式。通用的区间表示法中,圆括号表示 ‘排除’,方括号表示 ‘包括’。”1

    为了方便处理,我们这里如下形式化地定义一个区间:

    [x,y]={z|xzy,xN,yN,xy}

    (x,y)={z|x<z<y,xN,yN,xy}

    类似的,可以定义(],[)两种区间。
    所有的z可被称为区间[x, y]中的点,区间[x, y]称为一条线段。

    在下文中,如无特殊说明,均采用这种定义方式。

    1.2 区间的基本性质

    • 区间[x,y]的长度L([x,y])定义为区间集合的基数,即card([x, y])。不难发现,L([x,y])=card([x,y])=yx+1
    • 区间的交、并、补,均符合集合的定义。特别地,如果区间X = [a, b], Y = [c, d], 有XY=,称X和Y不相交,否则称X和Y 相交。如果ac,则有

    XYcb

    • 如果区间X = [a, b], Y = [c, d]有XY,则称X是Y的子区间。且

    XYac,bd

    • 定义权值函数为W:NR,W(x)表示x这一点的权值;定义区间的权值F(X)

    F(X)=i=abW(i),X=[a,b]

    为了叙述方便,对于一个有权值的区间[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];前缀和定义为区间前若干个数的权值和,P(i,X)=F([a,a+i1]),X=[a,b]。用O(n)的时间复杂度,不难求出区间[a, b]的前缀和。以下算法的正确性是显然的,可以用数学归纳法证明之,这里略去。

    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)
    

    差分是前缀和的逆运算,即给定前缀和构成的区间X={P(1,X),P(2,X)...P(L(X),X)},求出原始区间及权值,记作D(X)。不难发现,如下算法可以在O(n)的时间复杂度内求出D(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
    证明:由于上面求差分算法的正确性,我们不难发现,对于i(a,b],D[i]=P[i]P[i1]。由于P[i] += k, P[i-1] += k,因此D[i]不发生变化。由于P[a] += k, P[a-1]不变,所以D[a] += k;由于P[b] += k, P[b+1]不变,所以P[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数组分别从小到大排序,则有 i[1,n],ai<=bi
    证明:首先,a,b数组未排序时, i[1,n],ai<=bi,即至少存在一种使得 i[1,n],ai<=bi的排列(下面记为满足)。
    然后用反证法,假设对于已排好序的数组,i,ai>bi,则根据上述描述,通过将bkbj(k,j[1,m])调换若干次,则必定可以满足。下面分类讨论,如果将bibj交换,且交换量大小情况表示在数轴上如下各图所示:
    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
    以上已经穷举出所有可能的aj,bjai,bi的关系,但并不能使他们同时被满足。即经过若干次调换,总有一些区间无法满足ak<=bk。这与“必定存在一组满足的排列”的已知条件矛盾,故假设错误,原命题得证。

    定理2.2.2 记原所有区间的并为L,对于a,b的任何一种排列, i,aibii[1,n][ai,bi]=L
    证明:由于集合并的性质,X(YZ)=(XY)Z,因此只需要证明当n = 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)。如果采用基数排序,可以取得更好的效果。


    未完待续,慢慢填坑


    1. 引自wiki
  • 相关阅读:
    php中file_get_contents的图片乱码
    Lsh and Rsh (左移和右移)
    |=的含义
    Intercultural Communication Drama
    VS代码块快速折叠快捷键
    C++sqrt()函数与sqrtf()函数的区别
    C++中union和struct之间的区别
    泛型编程
    生成百度网盘目录文件
    字符串前L的含义
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684371.html
Copyright © 2020-2023  润新知