Sticks(DFS搜索+剪枝)

题目:

Sticks
Time Limit:1000MS Memory Limit:10000KB

Description
George took sticks of the same length and cut them randomly until all parts became at most 50 units long. Now he wants to return sticks to the original state, but he forgot how many sticks he had originally and how long they were originally. Please help him and design a program which computes the smallest possible original length of those sticks. All lengths expressed in units are integers greater than zero.

Input
The input contains blocks of 2 lines. The first line contains the number of sticks parts after cutting, there are at most 64 sticks. The second line contains the lengths of those parts separated by the space. The last line of the file contains zero.

Output
The output should contains the smallest possible length of original sticks, one per line.

Sample Input
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

Sample Output
6
5

翻译:

  • 现有n(n<=64)个切好的短木棒,每根短木棒长度为正整数(均<=50),将这些木棒组合成若干根等长的长木棒,且要求每根长木棒长度最小(也即组成的长木棒条数最多)
  • 例如sample1:共组成4根长木棒,每根长度为6。

题解:

  • 算法:比较容易可以看出这是一道搜索题,搜索的实质就是遍历,遍历分为DFS和BFS。此题要凑等长的木棒,要一根一根地组成,故用DFS比较好。
  • 数据结够:选用数组一位数组就足够。

接下来都是对如何搜索的优化。
1.组合好的长木棒(下称长木棒)等长,故长木棒的长度必须是所有短木棒之和的因子。
2.要求长木棒最短,所以对长木棒的长度从小到大遍历,一旦成功,即可省去下面的遍历。易知长木棒的长度区间在【最大的短木棒长度,所有短木棒长度之和】区间内。
3.每一次挑选短木棒,都先尽可能挑大的(因为小的灵活)。因此要对数组降序排序。
4.若加上一根短木棒长度超过了目前的搜索的长木棒长度,不用继续搜索。
5.遇到某根短木棒长度和前一根一样的情况,若前一根未被标记,则这一根不需要搜索,因为之前已经搜索过相同的长度的短木棒并且没有成功。

代码:

#include <iostream>
#include <string.h>
#include <algorithm>

using namespace std;

int n;//短木棍总数
int a[70];//a存储长度
int vis[70];//vis标记足迹
int flag;//是否成功
int x;//当前需要凑的总长

void dfs(int deep, int len, int num)//deep为用过短木棒总数,len为当前选取长度,num为小棒序号
{
    int i;
    if(flag) return;
    if(len==0)//处理每当一根长木棒拼好后,如何继续搜索
    {
        i=1;
        while(vis[i]) i++;//找到最大一根没用过的
        vis[i]=1;
        dfs(deep+1,len+a[i],i+1);//选取下一根
        vis[i]=0;//如果本递归退出了,说明当前选择拼凑X的方案不合法,消除足迹
        return;
    }
    if(len==x)
    {
        if(deep==n) flag=1;//成功标记
        else dfs(deep,0,0);//继续下一根拼凑
        return;
    }
    for(i=num;i<=n;i++)//从num号及之后选
    {
        if(!vis[i] && len+a[i]<=x)//没选过,且拼在一起长度不超过x
        {
            if(!vis[i-1] && a[i]==a[i-1]) continue;//如果和之前一根一样,不选(已经考虑下溢出)
            vis[i]=1;//标记
            dfs(deep+1,len+a[i],i+1);//继续选
            vis[i]=0;//上面退出,说明不合法(或者已经成功,不用在意),消除足迹
            if(len+a[i]==x) return;//正好就不用再继续了
            if(flag) return;//因为是退出出来的,所以检查flag,如果标记成功就可以一路退出去
        }
    }
    if(flag) return;
}

bool cmp(int a, int b)//a为前一个,b为后一个
{
    return a>b;
}

int main()
{
    while(true)
    {
        int sum=0;
        cin>>n;
        if(n==0) break;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];//读数
            sum+=a[i];//求和
        }
        sort(a+1,a+1+n,cmp);//sort函数的使用
        for(int i=a[1];i<=sum;i++)//i为原来的木棒数
        {
            if(sum%i) continue;//非因子肯定不是
            flag=0;
            memset(vis,0,sizeof(vis));//每次都要将标记数组清零
            x=i;
            dfs(0,0,1);//从用过0根棒,当前选取1号棒开始
            if(flag)
            {
                cout<<i<<endl;
                break;
            }
        }
    }
    return 0;
}

递推过程:

以sample1为例:
x=5(不是因子)
x=6:
dfs(0,0,1)->dfs(1,5,2)->dfs(2,6,8)->dfs(2,0,0)【第一根长木条拼成】->dfs(3,5,3)->dfs(4,6,9)->dfs(4,0,0)【第二根完成】->dfs(5,5,4)->dfs(6,6,10)->dsf(6,0,0)【第三根完成】->dsf(7,2,5)->dsf(8,4,6)->dsf(9,6,7)->flag=1,退出。

语法补充:

1.memset函数:
头文件:#include <string.h>
作用:对较大结构体或者数组进行清零(或者-1)

#include <string.h>
int op[14][24];
memset(op,0,sizeof(op));
memset(op,-1,sizeof(op));

2.sort函数
头文件:#include
作用:对给定区间所有元素排序,算法复杂度nlog(n)
形式:sort(start,end,cmp)默认升序,此时cmp省略;否则自定义bool cmp(int a, int b)比较函数

#include <algorithm>
int a[7]={4,2,6,1,3,6,7};
bool cmp(int a, int b)
{
	return a>b;//降序
}
sort(a,a+7,cmp);//对区间[0,7]的元素按降序排序

总结:

  • 要掌握基本的DFS算法的流程,足迹标记数组,设计参数,返回条件。
  • 有关剪枝:通过搜索顺序、终止条件的叠加,来优化搜索算法。

本文修改自链接:https://blog.csdn.net/ToBeYours/article/details/78753575

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值