您现在的位置是:首页 >其他 >洛谷P1120小木棍题解:DFS剪枝与优化网站首页其他

洛谷P1120小木棍题解:DFS剪枝与优化

少儿编程乔老师 2024-06-19 18:01:02
简介洛谷P1120小木棍题解:DFS剪枝与优化

题目链接

洛谷P1120 小木棍

题目描述

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 50 50

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入格式

第一行是一个整数 n n n,表示小木棍的个数。
第二行有 n n n 个整数,表示各个木棍的长度 a i a_i ai

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

9
5 2 1 5 2 1 5 2 1

样例输出 #1

6

提示

对于全部测试点, 1 ≤ n ≤ 65 1 leq n leq 65 1n65 1 ≤ a i ≤ 50 1 leq a_i leq 50 1ai50

算法思想(暴力枚举+DFS验证)

将一些同样长的小木棍随意砍成几段,然后拼接成原来的样子。给出每段小木棍的长度,求原始木棍的最小可能长度。根据题目描述可以得到下面的性质:

  • 原始长度 a n s ans ans一定大于等于每段小木棍的长度 a i a_i ai,即 a n s ≥ m a x { a i } ans ge max {a_i} ansmax{ai}
  • 原始长度 a n s ans ans一定小于等于每段小木棍的长度之和 t o t tot tot,即 a n s ≤ t o t ans le tot anstot
  • 每段小木棍的长度之和 t o t tot tot一定是原始长度 a n s ans ans的倍数,即 t o t % a n s = 0 tot \% ans = 0 tot%ans=0

基于上面的 3 3 3个性质和数据范围( 1 ≤ n ≤ 65 1 leq n leq 65 1n65 1 ≤ a i ≤ 50 1 leq a_i leq 50 1ai50),可以从小到大枚举原始长度 a n s ans ans,然后通过DFS验证答案是否合法。验证成功,即找到答案。

为了提高DFS的效率,可以从以下几个方面进行剪枝优化:

  1. 优化搜索顺序:对每一种方案,优先使用长木棍进行拼接,即从长到短地将木棍拼接起来。
  2. 避免重复搜索:若拼接当前木棍时已用了一根长为 x x x的木棍,递归时从上次使用的长度 x x x开始继续搜索。
  3. 合法性检查
    a. 使用记录每种长度的小木棍的出现次数。DFS枚举每段小木棍长度时,若没有该长度的小木棍或者该长度的小木棍都已使用完了,剪枝。
    b. 当前拼接的长度 l e n len len加上要拼接的小木棍长度 i i i超过原始长度 a n s ans ans时,剪枝。
  4. 可行性剪枝:使用长度为 i i i的小木棍拼接失败时,检查
    a. 如果已拼接的长度 l e n len len 0 0 0,说明当前小木棍无法完成拼接
    b. 如果拼接的长度 l e n len len与枚举的长度 i i i之和为原始长度时,其它更短的小木棍无法完成拼接
    结束搜索当前方案。

代码实现

#include <iostream>
using namespace std;
const int N = 100;
//b[i]表示长度为i的木棍个数
int minx = N, maxx, tot, b[N];
//一共拼k根原始木棍,当前已拼好的长度为len
//x表示当前使用的木棍的最大长度,ans表示木棍的原始长度
int dfs(int k, int len, int x, int ans) {
    //拼完所有原始木棍
    if(k == 0) return 1;
    //如果已拼好的长度等于原始木棍长度时,就拼下一个
    if(len == ans) return dfs(k - 1, 0, maxx, ans);
    //1.优化搜索顺序,从长到短地将木棍拼接起来
    //2.避免重复搜索,递归时从上次使用的长度x开始继续搜索
    for(int i = x; i >= minx; i --) {
        //3.合法性检查,如果不存在长度为i的木棒,或着拼完后长度超过原始木棍的长度
        if(b[i] == 0 || len + i > ans) continue;
        b[i] --; //使用了一根长度为i的木棍
        if(dfs(k, len + i, i, ans)) return 1;
        b[i] ++; //回溯,恢复状态
        //4.可行性剪枝
        if(len == 0 || len + i == ans) break;
    }
    return 0;
}
int main()
{
    int n, x;
    scanf("%d", &n);
    while(n --) {
        scanf("%d", &x);
        if(x > 50) continue;
        b[x] ++; //长度为x的木棍个数增加1
        tot += x; //累加所有木棍的长度
        maxx = max(maxx, x);
        minx = min(minx, x);
    }
    //枚举原始木棍的长度
    int ans;
    for(int i = maxx; i <= tot; i ++) 
    {
        //原始木棍的长度应该是总长度的约数
        if(tot % i != 0) continue; 
        //一共可以拼tot / i 根原始木棍,开始拼接的长度为0
        //每次搜索时从最长的木棍maxx开始拼
        //要拼的原始木棍的长度为i
        if(dfs(tot / i, 0, maxx, i)) 
        {
            ans = i;            
            break;
        }
    }
    printf("%d", ans);
    return 0;
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。