AcWing 167 木棒【DFS 剪枝☆】

题目描述:

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

输入格式

输入包含多组数据,每组数据包括两行。

第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。

输出格式

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围

数据保证每一节木棍的长度均不大于50。

输入样例:

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

输出样例:

6
5

分析:究极经典的剪枝题目。
从小到大去枚举每个可能的原木棒的长度,满足条件 len 为 总木棒的长度和sum的约数,然后通过dfs去判断此长度可不可行。
1、搜索顺序:递归参数为已经组成了多少根原木棒,递归函数从大到小去枚举每个木棒,去组成长度为len的原木棒。
2、剪枝与优化:
2-1、排除等效冗余:利用组合型递归枚举start,防止 123 组成原木棒 等同于 132。
2-2、可行性剪枝: 2-2-1: 当当前木棒失败,那么可以跳过和它相等长度的木棒,枚举下一根小木棒,因为一定也会失败。 2-2-2:当每根组成原木棒的第一根小木棒失败的时候,直接跳出此len方案。 2-2-3:如果当前木棍能恰好填满一根原始木棍(即组成原始木棍的最后一根小木棍),但因剩余的木棍无法组合出合法解而返回,用更短的木棍组合来代替这根木棍,他们的总长恰好是当前木棍的长度,但是由于这些替代木棍在后面的搜索中无法得到合法解,当前木棍也不可能替代这些木棍组合出合法解。因为当前木棍的做的事这些替代木棍也能做到。所以,当出现加上某根木棍恰好能填满一根原始木棍,但由在后面的搜索中失败了,就不必考虑其他木棍了,直接退出当前的枚举。
2-2-4:别漏了一个简单剪枝,即当前枚举的木棍加上组合的当前长度如果大于len,可以continue跳过,不需要做下去了。

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 70;

int sticks[N];
int n , sum , len ;
bool st[N];

bool dfs(int start , int s , int cnt){ //start为当前枚举的木棍,s为当前组合的长度,cnt为已经组成的木棒的根数,len为枚举的木棒长
    if( cnt * len == sum ) return true;
    if( s == len ) return dfs( 0 , 0 , cnt + 1);
    
    for(int i = start ; i < n ; i ++)
    {
        if( st[i] || sticks[i] + s > len ) continue;
        if( i > 1 && sticks[i] == sticks[i - 1] && st[i-1] == false) continue;
        st[i] = true;
        if( dfs(i+1 , s + sticks[i] , cnt ) ) return true;
        st[i] = false;
        
        if( s == 0 ) return false;
        if( s + sticks[i] == len ) return false;
    }
    
    return false;
}

int main(){
    
    while( cin >> n , n != 0)
    {
        sum = 0 ;
        memset( st , false ,sizeof st);
        for(int i = 0 ; i < n ; i ++) 
        {
            scanf("%d",&sticks[i]);
            sum += sticks[i];
        }
        
        //从大到小,优化搜索顺序
        sort( sticks, sticks+n);
        reverse(sticks,sticks+n);
        
        len = 1;
        while ( true )
        {
            if( sum % len == 0 && dfs(0 ,0 ,0 ) )
            {
                cout << len << endl;
                break;
            }
            len ++;
        }
        
        
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值