zoukankan      html  css  js  c++  java
  • 剪枝策略

    剪枝,顾名思义,就是通过一些判断,砍掉搜索树上不必要的子树。有时候,我们会发现某个结点对应的子树的状态都不是我们要的结果,那么我们其实没必要对这个分支进行搜索,砍掉这个子树,就是剪枝。

    可行性剪枝

    给定n个整数,要求选出K个数,使得选出来的K个数的和为sum。

    在搜索时,如果已经选了k个数,再往后选多的数是没有意义的。所以我们可以直接减去这个搜索分支。

    又比如,如果所有的数都是正数,如果一旦发现当前的和值都已经大于sum了,那么之后不管怎么选和值都不可能回到sum了,我们也可以直接终止这个分支的搜索。

    我们在搜索过程中,一旦发现如果某些状态无论如何都不能找到最终的解,就可以将其“剪枝”了。

    最优性剪枝

    对于求最优解的一类问题,通常可以用最优性剪枝,比如在求解迷宫最短路的时候,如果发现当前的步数已经超过了当前最优解,那从当前状态开始的搜索都是多余的,因为这样搜索下去永远都搜不到更优的解。通过这样的剪枝,可以省去大量冗余的计算。此外,在搜索是否有可行解的过程中,一旦找到了一组可行解,后面所有的搜索都不必再进行了,这算是最优性剪枝的一个特例。

    重复性剪枝

    对于某一些特定的搜索方式,一个方案可能会被搜索很多次,这样是没必要的。

    再来看这个问题:给定n个整数,要求选出K个数,使得选出来的K个数的和为sum。

    如果搜索方法是每次从剩下的数里选一个数,一共搜到第k层,那么1,2,3这个选取方法能被搜索到6次,这是没必要的,因为我们只关注选出来的数的和,而根本不会关注选出来的数的顺序,所以这里可以用重复性剪枝。

    我们规定选出来的数的位置是递增的,在搜索的时候,用一个参数来记录上一次选取的数的位置,那么此次选择我们从这个数之后开始选取,这样最后选出来的方案就不会重复了。

    当然,搜索的效率也要比直接二进制枚举更高。

    void dfs(int s, int cnt, int pos) {
        ...
        ...
        for (int i = pos; i <= n; i++) {
            if (!xuan[i]) {
                xuan[i] = true;
                dfs(s + a[i], cnt + 1, i + 1); // i + 1 表示从上一次选取的位置后面开始选
                xuan[i] = false;
            }
        }
    }

    从1,2,3,4……30这30个数中选取8个数使其和为200

     1 #include <iostream>
     2 using namespace std;
     3 int n, k, sum, ans;
     4 int a[40];
     5 bool xuan[40];
     6 void dfs(int s, int cnt, int pos)//多加一个参数,进行重复性剪枝 
     7 {
     8     if (s > sum || cnt > k) return;//可行性剪枝 
     9 
    10     if (s == sum && cnt == k) ans++;
    11     for (int i = pos; i < n; i++) 
    12     {
    13         if (!xuan[i]) 
    14         {
    15             xuan[i] = 1;
    16             dfs(s + a[i], cnt + 1, i + 1);
    17             xuan[i] = 0;
    18         }
    19     }
    20 }
    21 int main() 
    22 {
    23     n = 30;
    24     k = 8;
    25     sum = 200;
    26     for (int i = 0; i < 30; i++) 
    27         a[i] = i + 1;
    28     ans = 0;
    29     dfs(0, 0,0);
    30     cout << ans << endl;
    31     return 0;
    32 }

    奇偶性剪枝

    我们先来看一道题目:有一个n×m大小的迷宫。其中字符S表示起点,字符D表示出口,字符X表示墙壁,字符.表示平地。你需要从S出发走到D,每次只能向上下左右相邻的位置移动,并且不能走出地图,也不能走进墙壁。每次移动消耗1时间,走过路都会塌陷,因此不能走回头路或者原地不动。现在已知出口的大门会在T时间打开,判断在0时间从起点出发能否逃离迷宫。数据范围n,m≤10,T≤50。

    我们只需要用DFS来搜索每条路线,并且只需搜到T时间就可以了(这是一个可行性剪枝)。但是仅仅这样也无法通过本题,还需考虑更多的剪枝。

    如上图所示,将n×m的网格染成黑白两色。我们记每个格子的行数和列数之和x,如果x为偶数,那么格子就是白色,反之奇数时为黑色。容易发现相邻的两个格子的颜色肯定不一样,也就是说每走一步颜色都会不一样。更普遍的结论是:走奇数步会改变颜色,走偶数步颜色不变。

    那么如果起点和终点的颜色一样,而T是奇数的话,就不可能逃离迷宫。同理,如果起点和终点的颜色不一样,而T是偶数的话,也不能逃离迷宫。遇到这两种情况时,就不用进行DFS了,直接输出"NO"。

    这样的剪枝就是奇偶性剪枝,本质上也属于可行性剪枝。

    剪枝条件:(sx + sy + ex + ey + T) % 2 != 0

    例题

    正方形

    输入样例1

    4
    1 1 1 1

    输出样例1

    Yes

    输入样例2

    5
    10 20 30 40 50

    输出样例2

    No

    要一条边一条边地搜索,三条边一起搜索会超时,只需要搜索出前三条边即可。

    对于正方形的每一条边,我们能事先计算出长度。

    一条边一条边的进行搜索,当搜索到一条边满足长度要求的时候,重新从剩下的木棍中再搜索出一条边,直到搜索出四条边。

    像三角形那样同时搜索4条边会超时的。记得需要用重复性剪枝。

    ps:三角形那题三条边一起搜的写法

     1 #include <iostream>
     2 #include <cstdio>
     3 using namespace std;
     4 int l[30];
     5 int sum, n;
     6 bool ok;
     7 void dfs(int id, int l1, int l2, int l3) {
     8     if (l1 > sum || l2 > sum || l3 > sum) {
     9         return;
    10     }
    11     if (ok) {
    12         return;
    13     }
    14     if (id == n) {
    15         if (l1 == sum && l2 == sum && l3 == sum) {
    16             ok = 1;
    17         }
    18         return;
    19     }
    20     dfs(id + 1, l1 + l[id], l2, l3);
    21     dfs(id + 1, l1, l2 + l[id], l3);
    22     dfs(id + 1, l1, l2, l3 + l[id]);
    23 }
    24 
    25 int main() {
    26     freopen("triangle.in", "r", stdin);
    27     freopen("triangle.out", "w", stdout);
    28     int ca;
    29     cin >> n;
    30     sum = 0;
    31     for (int i = 0; i < n; ++i) {
    32         cin >> l[i];
    33         sum += l[i];
    34     }
    35     if (sum % 3) {
    36         cout << "no" << endl;
    37         return 0;
    38     }
    39     ok = 0;
    40     sum /= 3;
    41     dfs(0, 0, 0, 0);
    42     if (ok) {
    43         cout << "yes" << endl;
    44     } else {
    45         cout << "no" << endl;
    46     }
    47     return 0;
    48 }
    View Code

    另外可以提前判断一下,如果所有木棍的和不能被4整除,那么肯定不可能。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <iostream>
     4 #include <string>
     5 #include <math.h>
     6 #include <algorithm>
     7 #include <vector>
     8 #include <stack>
     9 #include <queue>
    10 #include <set>
    11 #include <map>
    12 #include <sstream>
    13 const int INF=0x3f3f3f3f;
    14 typedef long long LL;
    15 const int mod=1e9+7;
    16 const double PI = acos(-1);
    17 const double eps =1e-8;
    18 #define Bug cout<<"---------------------"<<endl
    19 const int maxn=1e5+10;
    20 using namespace std;
    21 
    22 int n,flag;
    23 int a[30];//存数据 
    24 int vis[30];//判断每条边访问过没 
    25 int le[4];//每条边的边长 
    26 int L;//标准边长 
    27 
    28 void DFS(int num,int pos)
    29 {
    30     if(num>3)//递归出口,已经搜索到了三条边 
    31     {
    32         flag=1;
    33         return ;
    34     }
    35     if(flag) return ;//最优性剪枝 
    36     if(le[num]>L) return ;//可行性剪枝
    37     if(le[num]==L) DFS(num+1,1);//找到了一条边
    38     else
    39     {
    40         for(int i=pos;i<=n;i++)
    41         {
    42             if(vis[i]==0)
    43             {
    44                 vis[i]=1;
    45                 le[num]+=a[i];
    46                 DFS(num,i+1);
    47                 le[num]-=a[i];
    48                 vis[i]=0;
    49             }
    50         }
    51     }
    52 }
    53 
    54 int main()
    55 {
    56     #ifdef DEBUG
    57     freopen("sample.txt","r",stdin);
    58     #endif
    59     ios_base::sync_with_stdio(false);
    60     cin.tie(NULL);
    61     
    62     scanf("%d",&n);
    63     for(int i=1;i<=n;i++)
    64     {
    65         scanf("%d",&a[i]);
    66         L+=a[i];
    67     }
    68     if(L%4==0)//可以分成4条边 
    69     {
    70         L/=4;
    71         DFS(1,1);
    72     }
    73     if(flag) printf("Yes
    ");
    74     else printf("No
    ");
    75 
    76     return 0;
    77 }
  • 相关阅读:
    linux系统更新及开启自动更新
    关于ICO的一些理解
    中小学教育缴费遇到的一些问题
    中小学教育缴费----支付宝回传数据.net core 接收中文乱码
    中小学教育缴费——验签失败
    C# MVC+EF—WebApi
    C# MVC+EF—页面搭建
    C# MVC+EF—结构搭建
    EF中的预先加载和延迟加载
    WebApi路由
  • 原文地址:https://www.cnblogs.com/jiamian/p/12176219.html
Copyright © 2011-2022 走看看