poj 1011 - sticks - dfs + 剪枝

2017-08-03 15:42:51

writer:pprp

之前写过搜索,但是都是很局限的那种,局限于图中的那种,今天是集训的第三天,今天主讲就是搜索,

现在水平还是很低,只能参考大佬们的代码才能大体理解代码,现在还是acm入门阶段,代码实现的能力还是太弱了,

即便是知道了思路,有时候也感觉很难实现,更何况很多时候都是想不到各种奇葩的思路;

我参考了两位大佬的代码:

1、http://www.cnblogs.com/zqy123/p/4921564.html

2、http://blog.sina.com.cn/s/blog_6635898a0100lgq0.html

第一位大佬关于搜索说的话感觉还是很有道理的:

  不要把搜索只理解为在一个地图上找点,其实搜索更可以抽象为当面对多个选择的时候如何抉择,深搜就是先认准一个方向走下去,不行再回来,走别的路;广搜就是把每一次能做的选择都试一遍。所以,这道拼木棍的题就是一道很好的深搜题。我们不知道哪根木棍可组合进来,那不如就按从前往后的顺序一个一个试。

  我这次合宿的那个兄弟这样跟我说dfs和bfs,dfs相当于是将所有情况用多个for循环表达出来,就是采用递归的方式,更容易理解,dfs是一搜索就到底,然后再返回换一条路进行搜索,我感觉这样理解还是很抽象,目前的我对这个理解还不够,之后多做点搜索的题;bfs就是用一个while循环还有队列,同时向所有可能的方向去寻找,直到找到答案;

  另外剪枝感觉还是很高深的,那些很简单的题意中如果不能发掘出剪枝,那很有可能就过不了;

  主要参考文献是第二位大佬的,写的比较清晰易懂,代码风格很好,让人很容易就能看懂;


 

现在开始说题目:

  题目大意:

    给出n根小棒的长度stick[i],已知这n根小棒原本由若干根长度相同的长木棒(原棒)分解而来。求出原棒的最小可能长度。

  算法分析:

    sum是棒子总长度,max是棒子中最长的那根,那么可以看出,最小可能长度应该是从max到sum的某一个值,并且sum应该是最小可能长度的整数倍;用dfs搜索看看有没有正好可以将某几根棒子放到一起就可以组成给定长度;

    关键是剪枝:

    1. 首先,应该从大到小进行排序,降序排序,优先选择将长一点的棒子作为拼接对象,就好像是在一个瓶子中放石头应该先放大的然后再放小的;
    2. 以当前最大的棒子作为拼接对象,去找另外的小棒子,如果恰好能将这个大棒子拼成最小可能长度,那么vis[i]数组记录下来已经用过的点,然后重新以目前最大的棒子进行开头;
    3. 如果棒子没有办法拼成最小可能长度,那么返回前一步,更改前一步的最小棒子的组合情况(不太理解??,有道理但是具体实现感觉还是不大理解),不用继续搜索下去了;//??
    4. 如果有相同长度的棒子,不用一个一个试了,比如:12,9,9,9,8,7,5...如果最大长度的棒子为12,如果跟9组合之后发现不能,那么不用与后边的9组合了,而是从8开始,这就减小了递归的次数;  

代码及分析如下:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 65;
const int INF = 0x3f3f3f3f;
int stick[maxn];
bool vis[maxn];
int len;
int n;
bool flag;//作为一个标记,一旦某一个len可以正好匹配,那么这个就是最小值

bool cmp(int a,int b)
{
    return a > b;
}

//used是已经被用过的小棒子数 ,u为当前要处理的小棒子数
//now_len是当前长度 ,初始时为 0 0 0
void dfs(int used,int now_len, int now)
{
    if(flag)return;

    if(now_len == 0)
    {
        int k = 0;
        while(vis[k]) k++;
        vis[k] = true;
        dfs(used + 1,stick[k], k + 1);
        vis[k] = false;
        return ;
    }
    else if(now_len == len)
    {
        if(used == n)
            flag = true;
        else
            dfs(used,0,0);
        return ;
    }
    else
    {
        for(int i = now ; i < n ; i++)
        {
              if(!vis[i] && now_len + stick[i] <= len)
              {
                    //最强剪枝,如果有相同的在后边,没有必要再配对
                    if(!vis[i - 1] && stick[i] == stick[i - 1])
                        continue;
                    vis[i] = true;
                    dfs(used + 1, now_len + stick[i],i + 1);
                    vis[i] = false;
              }
        }
    }

}

int main()
{
    while(cin >> n && n)
    {
        int sum = 0;
        flag = false;

        for(int i = 0 ; i < n ; i++)
        {
            cin >> stick[i];
            sum += stick[i];
        }

        //降序排序
        sort(stick,stick + n,cmp);

        for(len = stick[0] ; len < sum ; len++)
        {
            if(sum % len == 0)//枚举可能的长度
            {
                memset(vis,0,sizeof(vis));
                dfs(0,0,0);
                if(flag)break;
            }
        }
        cout << len << endl;
    }
    return 0;
}

这是我写的第一个搜索算法,虽然还是不太懂,看着大佬的写,之后我还会再重新看一遍的,有一天我一定也可以自己写出来搜索算法;

posted @ 2017-08-03 16:24  pprp  阅读(175)  评论(0编辑  收藏  举报