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

· · 题解

这就是一道DFS(大法师)

不要想多了

多想想剪枝!

附上题解,程序内有很详尽的注释(针对poj1011的,当然也有针对luogu的代码,附在里面了)

/*
POJ 1011
木棒
**************************
木棒

Time Limit: 1000MS
Memory Limit: 10000K
Total Submissions: 156283
Accepted: 37378

Description
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。每一节木棍的长度都用大于零的整数表示。

Input
输入包含多组数据,每组数据包括两行。第一行是一个不超过64的整数,
表示砍断之后共有多少节木棍。第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。
Output
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

Sample Input

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

Sample Output
6
5

**************************

luogu上的题目,可能会有长度>50的木棍存在,所以你们自己过滤下qwq
我就针对这道POJ上的题目写个题解

思路差不多嘛qwq
管理员不给过就熊你哈VwV
*/
#include<bits/stdc++.h>
using namespace std;
int n,lmark,ans,sum;
int nd;//当前枚举到的木棍长度
int a[66];//记录木棍长度
bool vis[66];//记录木棍有没有被用过
bool cmp(int x,int y) {//排序比较规则,将木棍从大到小排序
    return x>y;
}
bool dfs(int num,int len) {//num表示还有num根可以用,len表示还有len的长度就构造成1根完整的了
    if(num==0&&len==0)//如果木棍全部用完然后又构造好了木棍
        //由我们前面是计算除法,所以当num==0时,木棍的根数定然是最多的AwA
        return 1;
    if(len==0)len=nd;//构造完1根了,就再来一根
    int mark=1;//记录循环开始点
    if(len!=nd)mark=lmark+1;//如果不是一根全新的开始,就把mark(循环开始点)记为lmark+1(前面非重复木棍的位置)
    for(int i=mark; i<=n; i++)//剪枝,从前面非重复的位置开始循环 (枚举与当前木棍(已构成)进行匹配的木棍)
        if(vis[i]==0&&a[i]<=len) {//vis数组记录这个木棍有没有被用过
            if(i>1&&vis[i-1]==0&&a[i]==a[i-1])//剪枝;当出现重复木棍时,直接跳过
                //可以证明,在重复木棍中任取一根,不影响答案最终结果
                /*
                An example

                Input
                6
                1 1 1 2 2 2

                Output
                3

                可以见得,取第1根和取第2根和取第3根和后面的长度为2的木棍进行匹配,是不影响程序最终结果的
                重复的只计算第一根
                那么可以减少计算量
                由于我们已经排过序了
                那么判断重复就简单了
                */
                continue;
            vis[i]=1;//记为该木棒已经用过了
            lmark=i;//最后的不重复位置
            if(dfs(num-1,len-a[i]))//考虑下一根木棍
                return 1;
            else {
                vis[i]=0;//回溯
                if(len==a[i]||len==nd)
                    return 0;//len==a[i]说明当前木棍是被剩余的
                //len==nd是指当前木棍 无法被匹配(即上面的dfs(num-1,len-a[i])无法被触发)
                //那么当前答案肯定不是一种可行的方案
            }
        }
    return 0;
}
int main() {
    while(scanf("%d",&n),n) { //针对POJ的哦
        //如果是luogu上的话
        //应该介样子写
        //scanf("%d",&n);
        ans=sum=0;
        for(int i=1; i<=n; i++) {
            scanf("%d",a+i);
            sum+=a[i];//sum累计总长度,为后面的剪枝做好铺垫
        }//也是针对POJ的和蔼数据
        //对于luogu,你应该这样子写
        /*
            int cnt=n,n=0;
            for(int i=1;i<=cnt;i++)
            {
                int x;
                scanf("%d",&x);
                if(x<=50)a[++n]=x;
            }
        */
        sort(a+1,a+n+1,cmp);//排序,为去重做好铺垫
        for(int i=a[1]; i<=sum/2; i++) {//剪枝;每根木棍的长度只可能sum>=x>=min{a},且不可能在sum{a}/2-sum{a}之间
            //即若在sum{a}/2-sum{a} 之间时
            //定然会触发sun%i>0的条件
            //减少计算量qwq
            if(sum%i)continue;//剪枝;如果木棍的总长度无法整除当前枚举的长度
            //说明无论如何都无法构成完整的根
            memset(vis,0,sizeof(vis));//把标记数组清空
            lmark=1;
            nd=i;//记录下来枚举到的长度(我懒得开全局变量了2333)
            if(dfs(n,i)) {
                ans=i;
                break;//如果搜到了,因为我们是从小到大循环,如果找到了,那么这个解定然是最优解
                //直接记录下ans,就可以闪人了
            }
        }
        if(ans)printf("%d\n",ans);//如果ans有被更新过(即找到了比所有的凑成1根更好的答案),那么输出更好的答案
        else printf("%d\n",sum);//不然就把所有拼到一起
    }
    return 0;//完结撒fafa
}

其实蓝书(信息学奥赛一本通【提高篇】)上面有更多的剪枝

但是似乎有些剪枝奇奇怪怪

是错的呢qwq

好惹,就到这里啦。

                            我是分割线