hdu1455 poj1011 hoj1049 Sticks 深搜+剪枝

由于明天要给大一的同学讲搜索,所以临时放下了线段树,刷几个搜索题,结果这个题就从早上卡我到现在。

题意就是将一些值分成若干份求和使得他们的和相等,求出最小的能满足条件的和

这道题的关键之处在于剪枝:

1、这个和肯定整除所有数的总和,且这个和最小就是这些数中的最大值,所以枚举和的时候可以先剪枝

2、搜的过程中,如果当前的和加上剩下所有的和小于目标和,则直接返回0

3、当前和与目标和的差小于所有数的最小值,则直接返回0

4、在当前情况下,不对相同的值进行搜索,因为这显然是没有意义的

5、当前情况下,要搜索的数需要满足,加上当前和依然小于等于目标和

6、如果当前情况下,能够找到一个数,使得他加上当前和恰好等于目标和,那么如果这么搜下去不行的话,直接跳出当前循环,理由:

假设当前和比目标和小5,剩下的书中有5,2,2,1  那么可以有两种情况,要么直接拿5,要么拿2,2,1,但是,因为2,2,1相当于是5,但是他们比5更灵活,即在之后的情况下,2,2,1显然比5更有利,因为2,2,1既能合在一起当5用,又能拆开来用,也就是说,如果我现在拿了5找不到正确结果,我拿2,2,1更加不可能,所以,这种情况下,如果找不通,就没必要往下找了    (我被这步剪枝卡了很久,没有这步,在某些数据下会很慢,尤其是1很多的时候)

7、这些数是按从大到小排序的,这样能更加快的到达目标和

8、如果已经找到答案,直接退出深搜

9、每次从0开始搜的时候可以固定第一个值

-------------------------这样就能0.00s过了,其中1,4,6,7是主要的剪枝-----------------------

另外不可以一组一组深搜,即先找到一组和,再找下一组,我刚开始有个误区,以为组合的时候一定是字典序越大越好,即,我之前认为同样是9   5,3,1要优于5,2,2

但其实不能这样,这样虽然在不少情况下都是对的,还是能找到错误数据,poj的discuss里有数据,在此附上链接 http://poj.org/showmessage?message_id=172549

以下是AC代码:


#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
int a[100];
bool vis[100];
int sum[100];
int n;
bool findans;
int cmp(const int &a,const int &b)
{
    return a>b;
}
bool dfs(int k,int x,int y)
{
    if(findans) return 1; //剪枝8
    if(x==y)
    {
    	int m = -1;
        for(int i=0; i<n; i++)
        if(!vis[i]&&a[i]!=m)   //剪枝4
        {
        	m = a[i];
        	vis[i] = 1;
			bool f = dfs(i+1,x,a[i]);
			if(f)
			{
			    findans = 1; //剪枝8
			    return 1;
			}
			vis[i] = 0;  //回溯
			return 0;
        }
        findans = 1;//剪枝8
        return 1;
    }
    if(x-y>sum[n-1]-(k)?sum[k-1]:0) return 0; //剪枝2
    if(x-y<a[n-1]) return 0; //剪枝3
    int m=-1;
    for(int i=k; i<n; i++)
    if(!vis[i]&&a[i]!=m&&y+a[i]<=x) //剪枝4,5
    {
        m = a[i];
        vis[i] = 1;
        if(dfs(i+1,x,y+a[i]))
        {
            findans = 1;
            return 1;
        }
        vis[i] = 0;
        if(y+a[i]==x) return 0;  //剪枝6
    }
    return 0;
}
int main()
{
    while(scanf("%d",&n)&&n)
    {
        for(int i=0; i<n; i++)
        scanf("%d",&a[i]);
        sort(a,a+n,cmp); //剪枝7
        sum[0] = a[0];
        for(int i=1; i<n; i++)
        sum[i] = sum[i-1]+ a[i];
        bool res = 0;
        findans = 0;
        for(int i=a[0]; i<=sum[n-1]/2; i++)
        if(sum[n-1]%i==0)  //剪枝1
        {
            memset(vis,0,sizeof(vis));
            vis[0] = 1;
            if(dfs(1,i,a[0])) //剪枝8
            {
                printf("%d\n",i);
                res = 1;
				break;
            }
        }
        if(!res)  printf("%d\n",sum[n-1]);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值