剪枝策略


 

剪枝,顧名思義,就是通過一些判斷,砍掉搜索樹上不必要的子樹。有時候,我們會發現某個結點對應的子樹的狀態都不是我們要的結果,那么我們其實沒必要對這個分支進行搜索,砍掉這個子樹,就是剪枝。

 

可行性剪枝

給定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\n");
74     else printf("No\n");
75 
76     return 0;
77 }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM