小木棍(爆搜减枝)

题目描述

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

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

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

输入输出格式

输入格式:

共二行。

第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65N≤65

(管理员注:要把超过5050的长度自觉过滤掉,坑了很多人了!)

第二行为NN个用空个隔开的正整数,表示NN根小木棍的长度。

输出格式:

一个数,表示要求的原始木棍的最小可能长度

输入输出样例

输入样例#1: 复制

9
5 2 1 5 2 1 5 2 1

输出样例#1: 复制

6

此题明显是一道搜索剪枝题。此题难度应该不到提高+,但在考场上写这道题会吃力一些,因为不好调。我详细说一下这题的全思路,不过略长。

前排提示:第四条的优化7讲的是那个不少人不明白的优化,如果你只是不明白那个优化可以空降。

一,管理员已经在题目中告诉你输入时去掉长度大于50的木棍。

二,想好搜索什么。很明显我们要枚举把哪些棍子拼接成原来的长棍,而原始长度(原来的长棍的长度)都相等,因此我们可以在dfs外围枚举拼接后的每根长棍的长度。那枚举什么范围呢?

  其长度至少是最长的一根木棍,此时最长的这根木棍恰好单独组成原来的长棍。如果 原始长度 小于 最长的这根木棍,那么这根最长的木棍就无法自己或与其它木棍组成原来的长棍。

  其长度至多是所有木棍的长度之和,此时所有的木棍拼在一起恰好成为一根原来的长棍。如果 原始长度 大于所有木棍的长度之和,那么即使所有木棍拼在一起也组不够原来的长棍了。

  这么大的循环套dfs会超时么?当然会了。所以我们可以考虑到当 原始长度 不能被 所有木棍的长度之和 整除的话,这些木棍是拼不出整数根的(如果都拼成枚举的原来长棍的长度)。因此在循环时把它们刷掉。

  这里借鉴了dalao的(小)优化,即原始长度枚举到 所有木棍的长度之和/2 即可,因为此时所有木棍有可能拼成2根木棍,原始长度再大的话就只能是所有木棍拼成1根了。

三,脑补一下怎么搜。设dfs(int k,int last,int rest),k表示正在拼第几根原来的长棍,last表示使用的上一根木棍(输入的短棍)的编号,rest表示当前在拼的长棍还有多少长度未拼。于是循环枚举下一根将要使用的木棍。

四,上面的做法不超时说明你太强大了。你开始思考对程序做一些优化。(下面的优化请按顺序想)

1.一根长木棍肯定比几根短木棍拼成同样长度的用处小,即短的木棍可以更灵活组合,所以对输入的所有木棍按长度从大到小排序,从长到短地将木棍拼入,这样短木棍可以更加灵活地接在。

 如果你还不太清楚“灵活”的含义,请形象脑补一下——如果先用短木棍,那么需要很多根连续的短木棍接上一根长木棍才能拼成一根原来的长棍,那么短木棍都用了后,剩下了大量长木棍,拼起来就不如短木棍灵活,更难接出原始长度。而先用长木棍,最后再用短木棍补刀,这样就剩下了相对较短的木棍,能更加灵活地拼接出原始长度。

2.根据优化1,将输入的木棍从大到小排好序后,当用木棍i拼合原始长棍时,从第i+1根木棍开始往后搜。

3.当dfs返回拼接失败,需要更换当前使用的木棍时,不要再用与当前木棍的长度相同的木棍,因为当前木棍用了不行,改成与它相同长度的木棍一样不行。这里我预处理出了排序后每根木棍后面的最后一根与这根木棍长度相等的木棍(程序中的next数组),它的下一根木棍就是第一根长度不相等的木棍了。

 这个预处理可以优化时间,不必在循环中慢慢往下找长度不相等的木棍。

4.只找木棍长度不大于未拼长度rest的所有木棍。我看其他大部分人的做法(包括书上的啊)都是直接在循环中判断,但我认为可以根据木棍长度的单调性来二分找出第一个木棍长度不大于未拼长度rest。它后面的木棍一定都满足这个条件。

5.用vis数组标记每根木棍是否用过。另外在dfs回溯的时候别忘了去掉这些标记,这样就不用每次dfs之前memset了(memset用多的话速度可TM慢了)!

 优化5的习惯可以沿用到各种竞赛

6.由于是从小到大枚举 原始长度,因此第一次发现的答案就是最小长度。dfs中只要发现所有的木棍都凑成了若干根原长度的长棍(容易发现 凑出长棍的根数=所有木棍的长度之和/原始长度),立刻一层层退出dfs,不用滞留,退到dfs外后直接输出原始长度并结束程序。

7.还有一个难想却特别特别重要的优化:如果当前长棍剩余的未拼长度等于当前木棍的长度或原始长度,继续拼下去时却失败了,就直接回溯并改之前拼的木棍。有些人不太明白这个优化,这里简单说一下:

 当前长棍剩余的未拼长度等于当前木棍的长度时,当前木棍明显只能自组一根长棍,但继续拼下去却失败,说明这根木棍不能自组?!这根木棍不自组就没法用上了,所以不用搜更短的木棍了,直接回溯,改之前的木棍;

 当前长棍剩余的未拼长度等于原始长度时,说明这根原来的长棍还一点没拼,现在正在放入一根木棍。很明显,这根木棍还没有跟其它棍子拼接,如果现在拼下去能成功话,它肯定是能用上的,即自组或与其它还没用的木棍拼接。但继续拼下去却失败,说明现在这根木棍不能用上,无法完成拼接,所以直接回溯,改之前的木棍。

做了这么多优化可以确保飞跑了……搜索题啊,每招优化都要学,学一招说不定竞赛的时候就能跑的快一点。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
bool cmp(int x,int y)
{
    return x>y;
}
int n,t,sum=0,cnt=0,a[70],res,vis[70],pp;
int dfs(int len,int sta,int now)// dfs(ê£??μ?3¤?è£?μú??????1÷?aê?£?ò??-°úo?á???×é)
{
    if(now==res)
        return 1;
    if(len==0)
        if(dfs(pp,1,now+1))// μ±?°?aò??ùò??-°úíê ê£??μ?3¤?èó|????D??aê?
            return 1;
    for(int j=sta;j<=cnt;j++)
        if(!vis[j]&&a[j]<=len)
        {
            vis[j]=1; // ±£?¤μ??′±?ó?1y
            if(dfs(len-a[j],j+1,now)) // ????ò???μ?
                return 1;
            vis[j]=0;
            if(len==a[j]||len==pp)// ê×?è£o?ü??DDμ??a±í?÷×?′óμ?a[i]ò22??ü?ú×?ì??t è?oó£oè?1?len==pp ?′ò?×éD?μ???1÷ ???′ò?oóμ?ò22??ü?ú×?ì??t
                break; // ó?á?μ±?°??1÷oó?T·¨?′o?  μ?ê?ê£??μ???1÷3¤?è?1μèóúμ±?°??1÷3¤?è ì?3?
            while(a[j+1]==a[j]) // μ±?°3¤?è2?DD ????3¤?èò22?DD
                j++;
        }
    return 0;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&t);
        if(t<=50)
        {
            sum+=t;
            a[++cnt]=t;
        }
    }
    sort(a+1,a+1+cnt,cmp);
    for(int i=a[1];i<=sum;i++)
        if(sum%i==0)
        {
            pp=i; // è???????3¤?è
            res=sum/i;
            if(dfs(i,1,0))
            {
                printf("%d",i);
                return 0;
            }
        }
    return 0;
}

谢谢大家!

参考https://www.luogu.org/blog/complexity/solution-p1120

原文地址:https://www.cnblogs.com/mxrmxr/p/9818338.html

时间: 2024-11-05 13:38:02

小木棍(爆搜减枝)的相关文章

小木棍 爆搜剪枝

小木棍 爆搜剪枝 看了题解,用一个桶记录小木棍(很是机智,反正\(N\le50\)),然后就是爆搜剪枝了. 主要是注意一个优化思想:每次拼一个木棍时,一定先用大的去填再用小的去补. 递减遍历桶和下面这个最重要的剪枝都是这个优化思想 if(sum+i==per||sum==0) break; 连用大刚好填满这种最优方法都无法拼出,那更不用说先用小的去拼这种方法了. #include <cstdio> #include <cstdlib> #define MAX(A,B) ((A)&g

bzoj 2241: [SDOI2011]打地鼠(暴搜+减枝)

2241: [SDOI2011]打地鼠 Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 1098  Solved: 691 [Submit][Status][Discuss] Description 打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中.玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高. 游戏中的锤子每次只能打一只地鼠,如果多只地鼠同时探出头,玩家只能通过多次挥舞锤子的方

HDU 1010 深搜+减枝

HDU 1010 /************************************************************************* > File Name: HDU1010.cpp > Author:yuan > Mail: > Created Time: 2014年11月05日 星期三 22时22分56秒 **********************************************************************

[BZOJ1193][HNOI2006]马步距离 大范围贪心小范围爆搜

1193: [HNOI2006]马步距离 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1988  Solved: 905[Submit][Status][Discuss] Description 在国际象棋和中国象棋中,马的移动规则相同,都是走"日"字,我们将这种移动方式称为马步移动.如图所示, 从标号为 0 的点出发,可以经过一步马步移动达到标号为 1 的点,经过两步马步移动达到标号为 2 的点.任给 平面上的两点 p 和 s ,它

hdu5355 思维+爆搜

pid=5355">http://acm.hdu.edu.cn/showproblem.php?pid=5355 Problem Description There are?m?soda and today is their birthday. The?1-st soda has prepared?n?cakes with size?1,2,-,n. Now?1-st soda wants to divide the cakes into?m?parts so that the total

8/2 multi4 E找规律+模拟,空间开小了然后一直WA。。。J爆搜check不严谨WA。。。multi3 G凸包判共线写错数组名???样例太好过.想哭jpg。

multi4 Problem E. Matrix from Arrays 题意:构造一个数组,求子矩阵前缀和. 思路:打表找规律,"发现"L为奇数时循环节为L,为偶数时循环节为2L,求相应循环节的二维前缀和然后加加减减计算一下就好. 虚伪地证明一下循环节:L为奇数时对于第x行/列开始的位置有(x  +  x+L-1)*L/2   ->     (2x+L-1)/2(为整数)*L,因此扫过L行/列也就扫过了L整数倍"(2x+L-1)/2"倍的A[0]~A[L],

爆搜解hdu1572下沙小面的(2)

#include<iostream> #include<map> #include<string> #include<cstring> #include<cstdio> #include<cstdlib> #include<cmath> #include<queue> #include<vector> #include<algorithm> using namespace std; in

小木棍

传送门 这道题很明显是爆搜,但是爆搜肯定会T,我们来一步一步说. 首先木棍的原长不能小于所有木棍中最长的一段,否则那段就没用了.之后,木棍的原长也必须是总长的一个因子,而且必须是,小于等于他的一半的.这样已经减少一些不合法情况了,但是还远远不够. 之后我们考虑搜索时的操作,可以想到,我们应该先行使用长度较大的木棍,因为较小的木棍拼起来灵活一些,更适用于"补刀",拼成功的概率更大一些.之后还有一个非常重要的操作,就是如果当前正在拼的这个木棍的剩余长度等于原长或者和现在这根木棍长度相等,但

关于爆搜

关于爆搜 ? (这还用说,讲者太菜了) ? 爆搜通常是没有思路时一个 优秀 玄学的解题方法,但同样是搜索,我们所的分数却相差甚远,即搜索的优化问题; 前言 ? 这是很基础的东西,这里只作为回顾. ? 讲着实力不足,请不要D讲者; BFS ? BFS,广度优先搜索,用于逐层拓展的工具,可以有效地通过比较同一层之间的结果进行有效地减枝,而相比之下DFS的减枝就比较玄学,故能用BFS时,BFS的时间复杂度一般比DFS要低很多; ? BFS也是SPFA的实现基础 虽然SPFA已经死了 . ? BFS有双