【Acwing167】木棒(dfs+剪枝)超级详细题解!

题目描述

统一说明

本题思路来源于acwing算法提高课

木棍指题目输入数据所指的东西

木棒指最后由木棍拼接而成的最长的东西

看本文需要准备的知识

1.dfs基本思想

2.对"剪枝"这个词汇有一个基本的认识即可

整体分析

这个题目最终是求木棒的最短长度,所以我们可以从长度为1开始,每次加一,一直往后搜索,当搜索到解时,必然是最短长度。而在搜索的过程当中,显然长度满足:sum%length==0,这也是本题的一个小优化,dfs参数说明:

dfs(u,cur,start)

u:当前已经拼接好几根木棒

cur:当前正在拼接的这跟木棒已经拼好的长度

start:对同一根木棒,从哪一根木棍开始遍历

剪枝优化

A.优化搜索顺序

1.为了优先搜索分支较少的节点,我们可以让木棍按长度由大到小排序,优先搜索长度最长的木棍

B.排序等效冗余

1.按照组合数方式枚举,就是给每个木棍编号,有序的遍历,防止出现同一个木棒中由"1,2,3"拼成和"3,2,1"拼成的两种分支情况,毕竟一个木棒是如何拼成的跟木棍的顺序无关,所以对于同一根木棒拼接的时候,可以设置一个下标start,每次遍历木棍的时候从start开始遍历,即dfs(u,cur+wi,start+1)

2.如果当前木棍加到某个木棒上之后失败了,那么跟这个木棍相同长度的木棍加到这个位置的时候也会失败

C.可行性剪枝

1.如果一个木棍放在某一个木棒的第一个位置时失败了,那么就没有遍历剩下的木棍放在这个位置的必要了,也就是说这时候我们需要回溯了,什么意思呢,我用递归树的方式带领大家理解:

比如说A是在木棒x+1的第一个位置上放置木棍1,如果A的子树向A传递了false,那么接下来的路径就不是:"从A通过a回溯到D再通过b进入B"而是"直接通过a回溯到D再通过d回溯到E了",其中B是在木棒x+1的第一个位置上放置木棍2,C同理,D是在木棒x的最后一个位置上放置某一个木棍

如何证明上述的剪枝的正确性呢?反证法

假设在木棒x的第一个位置上放木棍1失败,但往后继续搜索还能发现最终的正确方案,那么这个木棍1,一定会被放置在后续木棒的某个位置上,假设这个木棒是p,这个位置是n,这时候,我们可以把木棒p上的第一个位置的木棍和n位置上的木棍1交换,然后再把木棒x和木棒p做一个位置交换,发现:此时木棒x的第一个位置上放的是木棍1!!!与假设矛盾,证毕

2.如果一个木棍1放在一个木棒x的最后一个位置,并且可以使这个木棒的最长度达到length,但是在这个状态节点的子树给这个节点返回了false,那么就可以直接向上面一样,在回溯到D之后直接回溯到E,而不是进入B!

证明:反证法

假设在满足上述情况下,还可以找到某一个或多个木棍的拼接使得木棒x拼成length,此时木棍1一定在下面的某一个木棒的某一个位置中,我直接把木棍1和x此时后面为length长度的一个或几个木棍的拼接对换,就会发现:木棍1放在木棒x的最后一个位置,并且可以使这个木棒的最长度达到length!!!与假设矛盾,证毕

想说的话

感觉这题确实有点抽象,特别是可行性剪枝的两个部分,所以我采用了从递归树的角度,从底层,分析了这个问题,如果有没有看懂的或者我错了的地方,拜托各路大佬在评论区指出,谢谢!

满分代码

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=70;
int w[N];
int sum,length;
bool st[N];
int n;

bool dfs(int u,int cur,int start)
{
    if(u*length==sum)return true;
    if(cur==length)return dfs(u+1,0,0);
    for(int i=start;i<n;i++)
    {
        if(st[i]||cur+w[i]>length)continue;
        st[i]=true;
        if(dfs(u,cur+w[i],start+1))return true;
        st[i]=false;
        
        if(!cur||w[i]+cur==length)return false;
        int j=i+1;
        while(j<n&&w[i]==w[j])j++;
        i=j-1;
    }
    return false;
}

int main()
{
    while(cin>>n,n)
    {
        memset(st,false,sizeof st);
        sum=0;
        for(int i=0;i<n;i++)cin>>w[i];
        for(int i=0;i<n;i++)sum+=w[i];
        sort(w,w+n);
        reverse(w,w+n);
        length=1;
        while(true)
        {
            if(sum%length==0&&dfs(0,0,0))
            {
                cout<<length<<endl;
                break;
            }
            length++;
        }
    }
    return 0;
}
相关推荐
LMT流氓兔2 小时前
第五单元——第4课:给工具参数加校验
java·前端·算法·ai·langchain
布莱克6052 小时前
理解 = delete 与 = default:两种赋值语义的差异
c++
物联网软硬件开发-轨物科技3 小时前
【轨物方案】深度解读开关柜智能感知及一键顺控专家系统
大数据·运维·网络·人工智能·算法·机器人
小小、码农3 小时前
[特殊字符]【C++11】类的新功能全面解析:移动构造、移动赋值、default、delete、final 与 override
linux·开发语言·网络·c++
行者全栈架构师4 小时前
我用 TRAE Work 把桌游之夜搬进了浏览器:一个计分器的从创意到上线
前端·算法·架构
YFJ_mily4 小时前
【10-12月优质学术会议合集】会议覆盖人工智能、大数据、高端装备、软件与网络安全 、计算机视觉技术等热门方向
算法·安全·web安全·网络安全·软件·rdlink研发家
库玛西4 小时前
外部排序全解析:败者树、置换-选择排序与最佳归并树
c语言·数据结构·c++·笔记·考研·算法
库玛西4 小时前
计算机网络第三章:数据链路层
服务器·网络·计算机网络·考研·算法·面试
邹霍梁@开源软件GoodERP4 小时前
【出海 ERP】本地没库存时:自产自销还是转口贸易?一张决策树给财务和 IT
算法·决策树·机器学习
程序 代码狂人4 小时前
经典 SQL 面试题:连续登录问题的两种经典解法
java·开发语言·算法