递归→P1145-数的划分

本文探讨了将整数n分成k份的不同分法数量计算问题,并给出了一种高效的算法实现方式。通过优化搜索路径,避免了不必要的计算,显著提高了算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【问题描述】

  将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序)。

  例如:n=7,k=3,下面三种分法被认为是相同的。

   1,1,5; 1,5,1; 5,1,1;

  问有多少种不同的分法。

【输入格式】

  两个整数:n,k。

【输出格式】

  一个整数,表示方案数

【输入样例】

7 3

【输出样例】

4

【数据范围】

  6 < n <= 200,2 <= k <= 6
  


【分析】
本题一种思路可以参考P1144整数划分
定义递归函数run(t,k,s)
→t为第几个加数,k为上一个加数,s为当前和
加数分别为a1,a2…ak
因为1,1,5; 1,5,1; 5,1,1视作同一种因此可以仿照整数划分搜索时
底层条件就是当s==n且t==k+1时cnt++

void f(int t,int p,int s)
{

    if(s==n)
    {
        if(t==k+1)//此处t+1是因为第t次最后一个数搜索完后会进入t+1次,若在t==k时就判断,则a[t]没有录入数据
        {
        cnt++;return;
        }
        else return;
    }
    for(int i=p;i<=n-s;i++)
    {
     a[t]=i;
     f(t+1,a[t],s+a[t]);
    }

不过经实际检验,因为6 < n <= 200,2 <= k <= 6
当数据稍微一大就会严重超时,干瞪着眼等着程序跑,所以进行一定程度的优化:

1.对于上面的算法,实则有很多种情况都是不必要的
例如1,1,1;1,1,2…..1,1,4只有1,1,5才是符合的
所以对于第t个数,我们可以直接令其为n-s
接着还要判断 if(n-s>=p)
最后成立才cnt++
2. 这道题与p1144不同的是,只需输出方案数,而不必输出每一种方案的情况,所以不必用定义数组保存(定义的话也无所谓)

这样一来,时间大大缩小,毕竟剪掉一大半不必搜索的路径
核心代码

void f(int t,int p,int s)//t为第几个加数,p为前一个加数,s为当前和 
{

    if(t==k)//分析最后一个加数 
    {
        a[t]=n-s;
        if(a[t]>=a[t-1]) cnt++;
        return;
    }

    for(int i=p;i<=n-s;i++)
     {

      a[t]=i;
      f(t+1,a[t],s+a[t]);
     }
}
<think>我们被要求解释Douglas-Peucker算法的原理及实现方法。根据引用,该算法用于图形据简化,特别是减少多边形或折线的点。 原理: Douglas-Peucker算法(也称为Ramer-Douglas-Peucker算法)是一种迭代端点拟合算法,用于减少构成曲线或折线的点。其基本思想是:对于一条折线,首先保留起点和终点,然后计算中间每个点到起点和终点连线的距离,找到距离最大的点。如果该最大距离大于设定的阈值,则保留该点,并以该点为界将折线分成两段,对每一段重复上述过程;如果最大距离小于阈值,则舍弃中间所有点,只保留起点和终点。 步骤: 1. 选择起点和终点:将折线的起点和终点连接成一条直线。 2. 计算最大距离:计算折线上所有中间点到这条直线的距离,并记录最大距离d_max及其对应的点P_max。 3. 判断阈值:如果d_max小于给定的阈值ε,则舍弃所有中间点,只保留起点和终点;否则,保留点P_max,并将折线为两段:起点到P_max和P_max到终点。 4. 递归简化:对割后的两段折线递归执行上述步骤,直到所有子段都处理完毕。 5. 合并结果:将所有保留的点按顺序合并,形成简化后的折线。 实现方法(以Python为例): 我们可以定义一个递归来实现。假设我们有一个点列表points和阈值epsilon。 注意:根据引用[3],我们计算点到直线的距离时,可以使用向量方法。这里我们采用引用[3]中的方法,但注意该代码中计算的是点到线段所在直线的距离(实际上是点在线段上的投影距离,但注意当线段长度为零时直接计算两点距离)。 然而,引用[3]中的距离计算函有误?实际上,点到直线的距离公式应该是:|(x0-x1)(y2-y1)-(x2-x1)(y0-y1)|/sqrt((x2-x1)^2+(y2-y1)^2),但引用[3]使用了向量点积除以模长得到的是投影长度(有向),然后没有取绝对值,并没有用这个投影长度和0以及线段长度比较来得到垂足是否在线段上。但是Douglas-Peucker算法通常计算的是点到线段两端点连线的垂直距离(即垂足在线段上的距离),但实际应用中,如果垂足不在线段上,则取到两端点的最小距离。然而,在Douglas-Peucker算法中,我们通常只计算点到由起点和终点确定的直线的垂直距离(不考虑垂足是否在线段上),因为算法要求的是整体形状的近似。 但是,根据引用[3]的代码,它计算的是点相对于线段方向的投影长度(即点在线段方向上的量),而不是垂直距离。实际上,点到直线的垂直距离应该用叉积的绝对值除以线段长度。所以我们需要修正。 修正后的距离计算(使用叉积): 点P到线段AB的距离,如果不考虑垂足是否在线段上,则垂直距离为:|(B-A)×(P-A)|/|B-A|(叉积的模除以线段长度)。但注意,如果线段AB长度为0,则直接计算两点距离。 因此,我们重新写一个计算点到直线(由两点确定)的距离的函。 但是,Douglas-Peucker算法中,我们要求的是点到起点终点连线的垂直距离(即垂线距离),所以用叉积方法。 实现步骤: 1. 定义计算点到直线距离的函。 2. 定义递归简化函,该函接收一个点列表和阈值,返回简化后的点列表。 但是递归可能导致栈溢出,所以我们可以用栈来模拟递归(非递归实现)。 这里我们给出递归实现(易于理解),并注意递归深度问题(对于点很多的情况,递归可能过深,可以考虑迭代实现)。 根据引用[2],我们还可以设定一个目标点(target_points)来调整阈值,但基本算法不涉及这个,我们只实现固定阈值的。 我们按照以下步骤: - 如果点列表长度小于等于2,则无需简化,直接返回。 - 否则,找到起点和终点,计算中间每个点到起点终点连线的距离。 - 找到最大距离的点及其距离。 - 如果最大距离大于阈值,则以此点为界,将折线分成两段(左段包括起点和该点,右段包括该点和终点),递归简化,然后合并(注意该点会被重复包含,合并时要去掉一个?但实际上我们递归处理左段时包括该点,右段也包括该点,合并时会出现重复,所以我们在合并时要去掉重复点?或者递归处理时左段包括该点,右段不包括该点?这样合并就不会重复。通常做法是:左段:起点到该点(包括该点),右段:该点到终点(包括该点),这样合并时该点会被包含两次,所以需要去重?或者我们可以调整区间,比如左段为[start_index, max_index],右段为[max_index, end_index],这样合并时中间点只出现一次。 具体实现时,我们可以这样划分:左段为points[0:max_index+1],右段为points[max_index:]。这样中间点points[max_index]在左右段都出现,但合并时我们只需要取一次。所以递归合并后,我们只需将左段和右段合并,注意右段去掉第一个点(因为它是左段的最后一个点)。 但是,为了简单,我们可以这样递归:左段递归结果(不包括最后一个点)加上右段递归结果。但这样会丢失中间点。所以我们可以这样:左段递归结果(包括最后一个点)加上右段递归结果(去掉第一个点)。这样合并后就没有重复点。 然而,递归返回的列表是简化后的点,我们合并时直接连接即可。 另一种简单做法:递归处理左段(从0到max_index)和右段(从max_index到末尾),然后合并时,左段的结果(包括max_index点)和右段的结果(从max_index开始,但右段结果中已经包含了max_index点,所以合并时去掉左段的最后一个点?)这样不行。所以我们可以将左段的结果(去掉最后一个点)和右段的结果合并,这样左段的结果包括起点到max_index(但不包括max_index?)这样不行。 所以,我们递归时,左段为points[0:max_index+1](包括max_index),右段为points[max_index:](包括max_index)。然后递归得到左段简化结果和右段简化结果。合并时,将左段结果(整个)和右段结果(去掉第一个点,即max_index点)合并,因为右段结果第一个点就是max_index。 或者,我们也可以不这样,直接合并,然后去重?但点可能重复,但坐标相同,所以去重也可以,但通常点列表中的点都是按顺序的,坐标可能相同但位置不同,所以不能去重。因此,我们采用:合并左段结果(0到max_index)和右段结果(max_index到末尾)时,左段结果保留,右段结果去掉第一个点(即max_index点)以避免重复。 但注意:左段结果已经包含了max_index点,右段结果也包含了max_index点,所以合并时,我们取左段结果和右段结果[1:](即去掉右段结果的第一个点)合并。 步骤: - 如果点<=2,直接返回(因为起点和终点必须保留,中间点可以去掉,但点2只有起点终点,点1直接返回)。 - 否则,计算中间所有点到起点终点连线的距离,找到最大距离点(记录索引和距离)。 - 如果最大距离大于阈值,则递归简化左段(points[0:max_index+1])和右段(points[max_index:]),然后合并:左段结果 + 右段结果[1:] - 如果最大距离小于等于阈值,则只保留起点和终点,即返回[points[0], points[-1]] 但是,这样递归可能会丢失最大距离点?不对,因为当最大距离大于阈值时,最大距离点会被保留(在左段结果中,因为左段包括该点,而递归左段时,该点会被保留?递归左段时,该点作为左段的终点,所以会被保留。同样,在右段中,该点作为起点也会被保留,但我们在合并时去掉了右段的第一个点,所以该点只被保留一次(在左段结果中)。 然而,我们递归处理左段时,左段的起点和终点别是原起点和最大距离点,递归处理左段会保留左段中需要保留的点。同样,右段起点是最大距离点,终点是原终点。 因此,我们按照这个思路实现。 但是,递归可能会导致重复处理,而效率可能不高。我们可以用栈来模拟递归(非递归),但这里为了清晰,先用递归。 代码实现(Python): 首先定义计算点到直线距离的函。 直线由点A和点B确定,点P到直线AB的距离公式为: distance = |(Bx-Ax)*(Ay-Py) - (Ax-Px)*(By-Ay)| / sqrt((Bx-Ax)**2 + (By-Ay)**2) 或者用叉积:向量AB和向量AP的叉积的绝对值除以|AB|。 即:distance = |(AB × AP)| / |AB| 向量AB = (Bx-Ax, By-Ay) 向量AP = (Px-Ax, Py-Ay) 叉积:AB × AP = (Bx-Ax)*(Py-Ay) - (By-Ay)*(Px-Ax) 所以:distance = |(Bx-Ax)*(Py-Ay) - (By-Ay)*(Px-Ax)| / sqrt((Bx-Ax)**2 + (By-Ay)**2) 注意:当线段AB长度为0时,直接计算点P到点A的距离。 我们写一个函来计算这个距离。 然后,递归实现Douglas-Peucker算法。 注意:为了避免浮点误差,我们使用一个很小的来避免除零。 但是,我们也可以使用引用[3]中的方法,但引用[3]的方法是计算投影长度(不是距离),我们这里需要垂直距离,所以用叉积方法。 下面我们给出Python代码:</think>### Douglas-Peucker 算法原理及实现方法 #### 算法原理 Douglas-Peucker 算法是一种迭代端点拟合算法,用于简化折线或多边形据点。其核心思想是通过保留关键特征点来减少据量,同时保持几何形状的总体特征。算法步骤如下[^1][^2]: 1. **起点终点连线** 将折线的起点($P_0$)和终点($P_n$)连接成直线 $L$。 2. **计算最大距离** 计算所有中间点($P_1$ 到 $P_{n-1}$)到直线 $L$ 的垂直距离 $d_i$,并找出最大距离 $d_{\max}$ 及其对应点 $P_k$。 3. **阈值判断** - 若 $d_{\max} > \varepsilon$($\varepsilon$ 为预设阈值),则保留 $P_k$,并以 $P_k$ 为界将折线为两段($P_0$ 到 $P_k$ 和 $P_k$ 到 $P_n$),递归处理子段。 - 若 $d_{\max} \leq \varepsilon$,则舍弃所有中间点,仅保留 $P_0$ 和 $P_n$。 4. **递归简化** 对每个子段重复步骤 1-3,直至所有子段处理完毕。 5. **合并结果** 将保留的点按原始顺序组合,形成简化后的折线。 #### 关键公式 - **点到直线距离计算**(向量法)[^3]: 设起点 $A(x_a,y_a)$,终点 $B(x_b,y_b)$,待计算点 $P(x_p,y_p)$: $$ d = \frac{\left| \overrightarrow{AB} \times \overrightarrow{AP} \right|}{\|\overrightarrow{AB}\|} $$ 其中: - $\overrightarrow{AB} = (x_b - x_a, y_b - y_a)$ - $\overrightarrow{AP} = (x_p - x_a, y_p - y_a)$ - 叉积 $\overrightarrow{AB} \times \overrightarrow{AP} = (x_b - x_a)(y_p - y_a) - (x_p - x_a)(y_b - y_a)$ - 模长 $\|\overrightarrow{AB}\| = \sqrt{(x_b - x_a)^2 + (y_b - y_a)^2}$ - **特例处理**:若 $A$ 和 $B$ 重合($\|\overrightarrow{AB}\| = 0$),直接计算 $P$ 到 $A$ 的欧氏距离: $$ d = \sqrt{(x_p - x_a)^2 + (y_p - y_a)^2} $$ #### Python 实现示例 ```python import numpy as np def perpendicular_distance(point, start, end): """计算点到直线的垂直距离""" if np.array_equal(start, end): return np.linalg.norm(point - start) # 处理起终点重合 # 向量计算 vec_ab = end - start vec_ap = point - start cross_product = np.abs(np.cross(vec_ab, vec_ap)) norm_ab = np.linalg.norm(vec_ab) return cross_product / norm_ab def douglas_peucker(points, epsilon): """递归实现 Douglas-Peucker 算法""" if len(points) <= 2: return points # 1. 确定起点终点 start, end = points[0], points[-1] # 2. 计算所有点到直线的距离 max_dist = 0 max_index = 0 for i in range(1, len(points)-1): d = perpendicular_distance(points[i], start, end) if d > max_dist: max_dist = d max_index = i # 3. 根据阈值判断 if max_dist > epsilon: # 递归处理子段 left_segment = douglas_peucker(points[:max_index+1], epsilon) right_segment = douglas_peucker(points[max_index:], epsilon) return left_segment[:-1] + right_segment # 避免重复点 else: return [start, end] # 舍弃中间点 # 示例使用 if __name__ == "__main__": # 原始点集 (示例据) points = np.array([ [0, 0], [1, 2], [2, 1], [3, 3], [4, 2], [5, 4], [6, 1], [7, 0] ]) epsilon = 1.0 # 简化阈值 simplified = douglas_peucker(points, epsilon) print("简化后的点:", simplified) ``` #### 关键参说明 1. **$\varepsilon$(epsilon)**: 控制简化精度的阈值: - $\varepsilon$ 越大 → 简化程度越高 → 保留点越少 - $\varepsilon$ 越小 → 几何特征保留越完整 → 保留点越多 2. **目标点(可选)**: 可结合二法动态调整 $\varepsilon$,使结果精确匹配目标点[^2]。 #### 应用场景 1. 地图据压缩(如 GeoJSON 多边形简化) 2. 交通轨迹抽稀(GPS 轨迹据压缩) 3. 计算机图形学(减少渲染负载) 4. 地理信息系统(GIS 据处理) 5. 手写识别(笔迹特征点提取)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值