最小M段和 O(nlogn)快速算法

先声明,这算法我还没弄出来

 

 

 

《数列分割》解题报告

[题目描述]

给出一个长为N的整数数列(N≤15000),要求求出最小的M,使得存在一种将数列分成恰好K份的方案(每份是数列上连续的一段,且不得为空),每份的数字之和不大于M

题目来源:Viet Nam Olympiad in Informatic 2005, Day I

[算法分析]

此题又是典型的要求最大值尽量小的形式,对于这样的问题,往往可以使用基于二分的算法解决。对于此题,则是二分枚举M的值,再判断对于这个M是否存在划分方案并缩小范围。

此题的难度在于,数列中可能存在负数,那么当数列的一个前缀的数字和已经大于M时,更后面的前缀和仍然有可能小于M(因为负数)。另外,对于一个特定的M,如果数列存在划分为K-1份的方案,并不一定存在划分为K份的方案,例如数列 -1 -1 -2,对于M-2,存在划分为2份的方案,却不存在划分为3份的方案。这样,一些常用的简单算法将不再适用。

在找不到有效算法时,先来看看朴素的算法。对于是否存在一种恰好K份的划分方案,使得每一份的数字和不大于M”这一问题,很容易设计出这样一个动态规划算法:

S[i]表示原数列前i个数构成的子序列,Sum[i]表示S[i]中所有数字之和。定义f[i][j]表示是否存在将S[i]划分为恰好j份的方案,其中每一份的数字之和不大于Mf[i][j]都为truefalse

对于j0f[i][j]当且仅当i0时为true,其余为false

对于i0f[i][j]当且仅当j0时为true,其余为false

对于j>0i>0f[i][j]f[i1][j-1] or f[i2][j-1] or … or f[il][j-1]ix是所有在[0i-1]范围内且满足Sum[i]-Sum[ix]≤M的整数。

这个算法的时间复杂度高达ON3),对于本题数据范围无法承受。但是容易发现,这个模型中状态形式简单(布尔类型),转移方程也比较特殊,可能会存在一些内在的规律。那么现在我们来深入研究这个动态规划模型。

转移方程的第一个特殊之处在于,对于同样的i不同的j,方程中的i1…il的值都是相同的

 

正在处理的是i7一行,对于这一行中的每个格子(状态),它的值由前面行中和它同色格子的值进行or运算得到,由于转移方程的特殊形式,就成了上图那样的形态。如果将每一行看成一个布尔向量,那么计算第i行实际上就是先找出i1…il,再将这l个向量相加(此处是or运算),并向右位移一位,最高位舍弃(实际上一定为false),最低位补0,就得到了第i个向量。

不过这一认识暂时不能帮助降低算法的时间复杂度,还需要更多的东西。回到转移方程中,观察i1…il这个集合,它看似是散乱的,其实不然,它是ix<iSum[i]-Sum[ix]≤M的集合,即Sum[i]-M≤Sum[ix],如果将0i-1行按照Sum值从大到小排序,那么i1…il对应的实际上就是前l

 

那么进行到第i行时,可以在0i-1行使用二分查找找出转移所涉及的行,将他们or起来、右位移得到第i行的结果,再将第i行插入到合适的位置,保持0i行按Sum降序排列。

上面的描述涉及的操作有:二分查找一个位置、将元素(布尔向量)插入到某个空的位置、求某个位置前所有向量or起来的值。这是典型的可以使用离散化+树状数组解决的问题,但是,由于操作的元素是向量,基本操作就有ON)的代价,导致算法的时间复杂度为ON2 log N),空间复杂度为ON2),对于本题的数据范围无法承受。

回到前面的算法分析,对于第i行,它的值是通过前面连续的lor起来再进行一次位移得到的,如果将得到的值和前面l行再or起来,相当于把一个向量右位移一个单位再和自己or起来,给人直观的感觉是,向量里的true不会变得更零散,而有可能变得更连续,特别地,如果这个向量里的true本来就是连续的一段,右位移再or的结果仍然是连续的。另外,第一行是连续的(只有1true),这启发我们可以使用数学归纳法来得到一些东西。稍作分析就能得到,在算法进行的任意时刻,任意一个向量里的true都是连续的,且按Sum值倒排后前任意多个连续向量or起来的结果里的true也是连续的。

有了这个结论,我们就可以使用一个整数对来描述一个向量:(XY)表示向量中从XY这一段为trueor运算和位移运算都可以简单地实现:

X1Y1orX2Y2)=(minX1X2),maxY1Y2))

XY>>1=(X1Y1

将这样的表示法用到前一个算法中,就得到了时间复杂度ON log N)、空间复杂度ON)的可行算法。

[总结]

本题具有这样低复杂度的算法的关键原因在于,每个向量里的true都是连续的,跳出上文的语境,就得到了一个数学命题:

要求将一个数列划分为每份数字之和不大于M的若干份,如果同时存在划分为A份和B份的方案,那么也存在划分为[AB]中的每一个份数的方案。

上文实际上证明了这个结论,但是通过了朴素的动态规划算法搭桥。惭愧的是我并没有找到使用纯数学工具进行证明的方法,希望各位大牛不吝赐教。

 

 

 

 

感谢xqz提供的程序

 

 

 

 

马上我就自己打一边然后发出来,不会让大家看这么丑的程序的......

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用分治法和数据预排序求解平面最邻近点对问题的 C 语言代码: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #define MAXN 10000 struct Point { double x, y; }; int cmp_y(const void *a, const void *b) { struct Point *p1 = (struct Point *)a; struct Point *p2 = (struct Point *)b; return (p1->y < p2->y) ? -1 : 1; } double dist(struct Point p1, struct Point p2) { double dx = p1.x - p2.x; double dy = p1.y - p2.y; return sqrt(dx*dx + dy*dy); } double min_dist(struct Point *a, int n) { if (n <= 1) return 1e9; if (n == 2) return dist(a[0], a[1]); int mid = n / 2; double midx = a[mid].x; double d1 = min_dist(a, mid); double d2 = min_dist(a + mid, n - mid); double d = fmin(d1, d2); struct Point *b = (struct Point *)malloc(n * sizeof(struct Point)); int m = 0; for (int i = 0; i < n; i++) { if (fabs(a[i].x - midx) < d) { b[m++] = a[i]; } } qsort(b, m, sizeof(struct Point), cmp_y); for (int i = 0; i < m; i++) { for (int j = i + 1; j < m && b[j].y - b[i].y < d; j++) { double d3 = dist(b[i], b[j]); if (d3 < d) d = d3; } } free(b); return d; } int cmp_x(const void *a, const void *b) { struct Point *p1 = (struct Point *)a; struct Point *p2 = (struct Point *)b; return (p1->x < p2->x) ? -1 : 1; } int main() { int n; struct Point a[MAXN]; scanf("%d", &n); for (int i = 0; i < n; i++) { scanf("%lf%lf", &a[i].x, &a[i].y); } qsort(a, n, sizeof(struct Point), cmp_x); double d = min_dist(a, n); printf("%.2lf\n", d); return 0; } ``` 该程序首先读入点的数量和坐标,然后按照 x 坐标从小到大排序。然后调用 min_dist 函数求解最邻近点对的距离,并输出结果。该函数使用递归的方法,先将点集分成两半,然后分别在左右两半中递归求解最邻近点对的距离。接下来考虑跨越两个子集的点对,需要在 x 坐标在 [mid - min_dist, mid + min_dist] 范围内的点中,按照 y 坐标从小到大排序,然后依次判断相邻的点对之间的距离,更新最小距离即可。与不使用数据预排序的算法不同,该程序在排序后的点集上进行求解,因此时间复杂度为 O(nlogn)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值