题解 P1120 【小木棍 [数据加强版]】

· · 题解

这一道题一看数据,很明显的剪枝+深搜。

主要思路:枚举最终小木棍的长度 ,搜索能不能拼出K根但是会超时……于是就需要剪枝。

几个注意点:

  1. 原话:管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!

  2. 剪枝要剪到极致。

剪枝点:

  1. 如果第i个棍子不能拼成假设的长度,则和第i个棍子相同长度的棍子也是不可能的,所以可以直接跳过去的!(很明显)

  2. 将所有题目给的棍子的长度按照从大到小的顺序排列,然后按照此顺序进行深搜。(为的是给后边的小木棍更多的机会,减小搜索树的大小)

  3. 替换第i根棍子的第一根木棒是没用的,替换第i根的最后一根棒子也是没用的(这一根不行,将其换成两根小木棍的和也是没用的)

  4. 只需枚举总和的因子。(因为是分成等长的段数)

  5. 如果某次拼接选择长度为S的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要跳过所有长度为S的木棒。(很显然)(这一点我没有用,很可惜,如果有了这个,时间会更快。

献上代码:

/*剪枝点会在代码后用【x】标出*/
#include<bits/stdc++.h>
using namespace std;
int n,mg[100],tmp,top=1,tt=0;
/*mg是木棍,tmp是转化用变量,top是木棍个数+1(左闭右开)
tt是总长
*/
bool vis[100];//判断该木棍是否用过
int cmp(int a,int b){
    return a>b;//比较函数
}
bool dfs(int num/*剩几根*/,int len/*大棍长*/,int rest/*剩余长*/,int last/*上一根棍子*/){

    if((rest==0)&&(num==0)) return 1;//放完了

    if(num==0) return 0;//放完了但还有剩余

    if(rest==0) {//一根分完了
        rest=len;//切到下一根
        last= 0;
    }//开始新的一根

    for(int i=last+1;i<top;i++){//从上一个长度往后搜,也是剪枝
        if(vis[i]) continue;//使用过了,直接跳过
        if(mg[i]>rest){//剪枝【1】
        while(mg[i]==mg[i+1]&&i<top) i++;//切到下一根不同长的木棍
        continue;
        }
        vis[i]=1;//摆上
        if(dfs(num-1,len,rest-mg[i],i)) return 1;//如果它后边的木棍可以摆成,那么它就可以
        vis[i]=0;//拿掉
        if((mg[i]==rest)||(len==rest)) break;//剪枝【3】
        while(mg[i]==mg[i+1]&&i<top) i++;//同上
    }
    return 0;
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&tmp);
        if(tmp<=50)//按照管理员的话说,筛一下
            mg[top++]=tmp,tt+=tmp;
    }

    sort(mg+1,mg+top,cmp);//降序排序

    for(int i=mg[1];i<=tt;i++){
        if(tt%i!=0) continue;//剪枝点【4】
        if(dfs(top-1,i,i,0)){//找到最小长
            /*
            因为可行木棍长不一定连续,
            所以要从1来寻找最小值。
            */
            printf("%d",i);
            return 0;
        }
    }

    return 0;
}
以此纪念我那逝去的40分钟。谢谢。