剪枝策略
剪枝,顧名思義,就是通過一些判斷,砍掉搜索樹上不必要的子樹。有時候,我們會發現某個結點對應的子樹的狀態都不是我們要的結果,那么我們其實沒必要對這個分支進行搜索,砍掉這個子樹,就是剪枝。
可行性剪枝
給定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 }
總結
- 上一篇: xlwings 操作 excel
- 下一篇: TCP ------ TCP四次挥手(断