搜索:重复性剪枝 (poj1011)

Posted sun897949163

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搜索:重复性剪枝 (poj1011)相关的知识,希望对你有一定的参考价值。

POJ 1011

在民国某年,少林寺被军阀炮轰,这些棍子被炸成 N 节长度各异的小木棒

战火过后,少林方丈想要用这些木棒拼回原来的棍子

可他记不得原来到底有几根棍子了,只知道古人比较矮,且为了携带方便,棍子一定比较短

他想知道这些棍子最短可能有多短

分析

·
·

尝试 (枚举) 什么?

枚举所有可能的棍子长度

从最长的那根木棒的长度一直枚举到木棒长度总和的一半

对每个假设的棍子长度,试试看能否拼齐若干根棍子

·
·

真的要每个长度都试吗?

对于不是木棒总长度的因子的长度,可以直接否定,不需尝试

·
·

假设了一个棍子长度的前提下,如何尝试去拼成若干根该长度的棍子?

一根一根地拼棍子

如果拼好前i根棍子,结果发现第i+1根无论如何拼不成了 
    →推翻第i根的拼法,重拼第i根…..

直至有可能推翻第1根棍子的拼法

·
·

本题真正应该设置的状态是什么

状态可以是一个二元组 (R, M)

R : 还没被用掉的木棒数目
M : 当前正在拼的棍子还缺少的长度

初始状态和搜索的终止状态(解状态)是什么?

假设共有N节木棒,假定的棍子长度是L:

初始状态: (N, L)
终止状态: (0, 0)

·
·
·

剪枝方案

·
·

不要在同一个位置多次尝试相同长度的木棒、

如果某次拼接选择长度为S 的木棒,导致最终失败,则在同一位置尝试下一根木棒时,要跳过所有长度为S 的木棒

·
·

不考虑替换第i根棍子中的第一根木棒(换了也没用)

可以考虑把木棒2, 3换掉重拼棍子i,但是把2, 3都去掉后,换1是没有意义的

因为假设替换后能全部拼成功,那么这被换下来的第一根木棒,必然会出现在以后拼好的某根棍子k中

那么我们原先拼第i根棍子时, 就可以用和棍子k同样的构成法来拼,照这种构成法拼好第i根棍子,继续下去最终也应该能够全部拼成功

这就是一种去重复性的搜索
·
·

不要希望通过仅仅替换已拼好棍子的最后一根木棒就能够改变失败的局面

假设替换3后最终能够成功,那么3必然出现在后面的某个棍子k里

将棍子k中的3和棍子i中用来替换3的几根木棒对调,结果当然一样是成功的

这就和i原来的拼法会导致不成功矛盾
·
·

确保长度是从长到短排列

木棒3 比木棒2长,这种情况的出现是一种浪费

因为要是这样往下能成功,那么2, 3 对调的拼法肯定也能成功。

由于取木棒是从长到短的,所以能走到这一步,就意味着当初将3放在2的位置时,是不成功的

具体方法:

为此,要设置一个全局变量 nLastStickNo,记住最近拼上去的那条木棒的下标。
·
·

代码

#include <iostream>
#include <memory.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
using namespace std;
int N;
int L;
vector<int> anLength;
int anUsed[65]; //是否用过的标记
int i, j, k;
int nLastStickNo;

bool cmp(int a, int b)

    return a > b;


int Dfs(int R, int M)

    if (R == 0 && M == 0)
        return true;
    if (M == 0) //一根刚刚拼完
        M = L; //开始拼新的一根
    int nStartNo = 0;
    if (M != L) //剪枝4
        nStartNo = nLastStickNo + 1;
    for (int i = nStartNo; i < N; i++) 
    
        if (!anUsed[i] && anLength[i] <= M) 
        
            if (i > 0)
            
                if (anUsed[i - 1] == false
                    && anLength[i] == anLength[i - 1])
                    continue; //剪枝1
            
            anUsed[i] = 1; nLastStickNo = i;
            if (Dfs(R - 1,M - anLength[i]))
                return true;
            else 
                anUsed[i] = 0; //说明本次不能用第i根
                               //第i根以后还有用
                if (anLength[i] == M || M == L)
                    return false; //剪枝3, 2
            
        
    
    return false;


int main()

    while (1) 
        cin >> N;
        if (N == 0)
            break;
        int nTotalLen = 0;
        anLength.clear();
        for (int i = 0; i < N; i++) 
            int n;
            cin >> n;
            anLength.push_back(n);
            nTotalLen += anLength[i];
        
        sort(anLength.begin(), anLength.end(),cmp); //要从长到短进行尝试
        for (L = anLength[0]; L <= nTotalLen / 2; L++) 
            if (nTotalLen % L)
                continue;
            memset(anUsed, 0, sizeof(anUsed));
            if (Dfs(N, L)) 
                cout << L << endl;
                break;
            
        
        if (L > nTotalLen / 2)
            cout << nTotalLen << endl;
     // while
    return 0;

剪枝总结

1)选择特定的搜索顺序

如果一个任务分为 A, B, C…..等步骤(先后次序无关)

要优先尝试可能性少的步骤

这样可以尽早的排除不可能的情况, 减少搜索量

·
2)要发现表面上不同,实质相同的重复状态

避免重复的搜索, 就上上文对于第一根和第二根棍子的更换

·
3)要根据实际问题发掘剪枝方案

废话。。。

以上是关于搜索:重复性剪枝 (poj1011)的主要内容,如果未能解决你的问题,请参考以下文章

POJ - 1011 剪枝练习

POJ1011 木棒(dfs+剪枝)

POJ1011 木棒(dfs+剪枝)

poj1011 Sticks

poj(1011)——Sticks(经典的dfs+剪枝)

POJ1011 (深搜+剪枝)