搜索中的剪枝

一般来说剪枝有以下几类

(1)优化搜索顺序

比如一些有多个物品然后凑重量的题就可以重量大的优先,根据重量排序

(2)排除等效冗余

这是我最容易忽略的一点。在拼木棍那题中有淋漓尽致的体现

(3)可行性剪枝

如果当前无论如何都无法到达递归边界就剪掉

(4)最优性剪枝

这个就很常见了,形如if(now >= ans) return

因为无论如何都无法有更优的答案

(5)记忆化

如果会重复遍历状态,那就可以用记忆化

 

几道例题

poj 1011

http://poj.org/problem?id=1011

非常经典的一道题,拼木棍

枚举答案len,用搜索去判断能不能拼成

(1)优化搜索顺序

长度从大到小排序

(2)排除等效冗余

先用a再用b和先用b再用a是一样的,所以可以规定从上一根木棍的下一根开始拼

如果一根木棍去拼不能拼成,那么之后同样长度的木棍一样也不能,要剪去

如果当前已经拼了的长度为0,而又无法拼成,那么剪掉,因为这个时候木棍的选择是最多的,这个时候都不行,继续下去木棍选择变少更不行

 

#include<cstdio>
#include<algorithm>
#include<functional>
#include<cstring>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

const int MAXN = 100 + 10;
int a[MAXN], vis[MAXN], len, n, num;

bool dfs(int cnt, int last, int now)
{
    if(cnt == num) return true;
    if(now == len) return dfs(cnt + 1, 1, 0);
    
    int fail = 0; //剪枝  
    _for(i, last, n) //剪枝  
        if(!vis[i] && now + a[i] <= len && fail != a[i])
        {
            vis[i] = 1;
            if(dfs(cnt, i + 1, now + a[i])) return true;
            vis[i] = 0;
            fail = a[i];
            if(now == 0) return false; ////剪枝  
        }
        
    return false;
}

int main()
{
    while(~scanf("%d", &n) && n)
    {
        int sum = 0, maxt = 0;
        _for(i, 1, n) 
        {
            scanf("%d", &a[i]);
            sum += a[i];
            maxt = max(maxt, a[i]);
        }
    
        sort(a + 1, a + n + 1, greater<int>()); //剪枝 
        for(len = maxt; len <= sum; len++) //枚举答案 
            if(sum % len == 0)
            {
                num = sum / len;
                memset(vis, 0, sizeof(vis));
                if(dfs(0, 1, 0)) break;
            }
            
        printf("%d\n", len);
    }
    
    return 0;
}

 

[NOI1999]生日蛋糕

https://www.luogu.org/problemnew/solution/P1731

这道题自己独立做的时候拿了70分,少想到了一个可行性剪枝

总结一下

(1)对于最优性剪枝,可以预处理出剩下情况下最优的答案来判断

(2)关于可行性剪枝,可以从最大和最小来判断。剩余情况最大都小于目标状态,剪掉,

剩余状况最小都大于,目标状态,剪掉

#include<cstdio>
#include<algorithm>
#define REP(i, a, b) for(register int i = (a); i < (b); i++)
#define _for(i, a, b) for(register int i = (a); i <= (b); i++)
using namespace std;

int n, m, ans, v[25], g[25]; 

void dfs(int cnt, int R, int H, int V, int S)
{
    int left = m - cnt;
    if(S + g[left] >= ans) return;         //最优性剪枝
    if(V + v[left] > n) return;            //可行性剪枝           
    if(V + R * R * H * left < n) return;   //可行性剪枝,这个我漏了 
    
    if(cnt == m) 
    { 
        if(V == n) ans = S; 
        return; 
    }
    
    for(int r = R - 1; r >= left; r--)  //可行性剪枝 
        for(int h = H - 1; h >= left; h--) //可行性剪枝  
            if(V + r * r * h <= n)
                dfs(cnt + 1, r, h, V + r * r * h, S + 2 * r * h);
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, 20) v[i] = i * i * i;
    _for(i, 1, 20) g[i] = 2 * i * i;
    
    ans = 1e9;
    for(int h = n / (m * m); h >= m; h--)
        for(int r = m; r * r * h <= n; r++)
            dfs(1, r, h, r * r * h, r * r + 2 * r * h);
    printf("%d\n", ans == 1e9 ? 0 : ans);
    return 0;
}

 

posted @ 2018-11-02 07:49  Sugewud  阅读(292)  评论(0编辑  收藏  举报