1. 程式人生 > 實用技巧 >深搜的剪枝技巧

深搜的剪枝技巧

10018. 「一本通 1.3 例 1」數的劃分
mind

因為是要求不相同,可以發現只需求個升序的排列就行了(廢了好大勁)

神奇剪枝:

 for(int i = last; sum + i*(k - now) <= n; i++)

列舉下一位數的時候確定其邊界,升序,所以至少比前一個大,還要維護後面遞增

/*
work by:Ariel_
*/
#include<iostream>
#include<cstdio>
using namespace std;
int n,k,ans;
void dfs(int last,int sum,int now){
	if(now == k){
        if(sum == n)ans++;
		return;
	}
    for(int i = last; sum + i*(k - now) <= n; i++){//剪枝 
    	   dfs(i , sum + i,now + 1);
	}
}
int main(){
   scanf("%d%d",&n,&k);
   dfs(1, 0, 0);
   printf("%d",ans);
}

10019「一本通 1.3 例 2」生日蛋糕
mind:

搜尋剪枝

1.當前的奶油麵積+之後的最小奶油麵積>現在已求出的的最小奶油麵積—直接return;

2.當前的體積 > n,return;

3.當前的體積 + 之後的最大體積<體積總數,直接return;

4.發現每次列舉半徑和高時,是從上一個的半徑和高,到還剩下的層數,是因為每一層的半徑和高都要比下一層的小1,所以你得每一層都留一個1,so,是從上一個的半徑和高,到還剩下的層數;

剪枝1
if(k + z + r[1]*r[1] > minn) return ;
剪枝2
if(y > n) return;
剪枝3
if(y - (r[x - 1])*(r[x-1])*(h[x-1])*z > 0)return;
/*
work by:Ariel_
圓柱
    V:π* R^2*H
    S表: 2*π*R * H
	S底:π* R^2
變數:
     k當前表面積;
     x為當前為第幾個蛋糕
     y為當前的體積
     z表示層數 
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#define inf 0x3f3f3f3f
using namespace std;
//===============================================================快讀 
int read(){
	int x = 0,f = 1;char c = getchar();
	while(c < '0'|| c > '9'){if(c == '-1') f = -1;c = getchar();}
	while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
	return x * f;
} 
int n,m,r[21],h[21],minn;
void dfs(int x,int y,int k,int z){
	 if(y == 0 && x == m + 1){
	 	  k += r[1]*r[1];
	 	  if(k < minn) minn = k;
	 	  return ;
	 }
	 if(k + z + r[1]*r[1] > minn) return ;
	 if(y - (r[x - 1])*(r[x-1])*(h[x-1])*z > 0)return;
	 for(int i = r[x-1] - 1;i >= z; i--){//列舉半徑 
	 	 for(int j = h[x - 1] - 1;j >= z; j--){//列舉高度 
	 	 	   if(y - i * i * j >= 0 && x + 1 <= m + 1){
	 	 	   	     r[x] = i,h[x] = j;
	 	 	   	     dfs(x + 1,y - i * i * j,k + (i*2*j),z - 1);
	 	 	   	     h[x] = 0;
	 	 	   	     r[x] = 0;//回溯 
				 }
		  }
	 } 
}
int main(){
   n = read(),m = read();
   minn = inf;
   r[0] = (int)sqrt(n);
   h[0] = (int)sqrt(n);
   dfs(1, n, 0, m);
   if(minn == inf) printf("%d",0);
   else printf("%d",minn);
}

小木棍
mind:

dfs剪枝

剪枝1:

優化搜尋順序,優先嚐試較長的木棍

sort(a + 1,a + n + 1);
剪枝2

對於每根木棒,fail記錄的是最近一次嘗試拼接的木棍長度。這樣再回溯時就不會再嘗試相同長度的木棍

int fail=0;
============================
fail=a[i];
剪枝3

限制先後加入一根原始木棍的長度是遞減的。因為先拼上一個長為x的木棍再拼上一個長為y的木棍,等效於先拼上一個長為 y 的木棍再拼上一個長為x的木棍。所以只需要搜尋其中一種即可

for(int i = last;i <= n;i++)
剪枝4|5

第四個剪枝開始了:如果在一根原始木棒中嘗試拼接的第一根木棍的遞迴分支就以失敗返回,直接判斷當前分支無解。與此同時,第五個剪枝開始了,如果兩個木棍的長度和與一個木棍的一樣,只嘗試一個的就行了(因為前兩個可能會有更大的效用)

if(ll==0 || ll + a[i] == len)
	 return false;

node

/*
work by:Ariel_
變數:
stick 即正在拼第stick根木棒(確保前面的都拼好了)
第stick根木棒的當前長度為ll
拼第stick根木棒的上一根小木棒為last
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define inf 0x3f3f3f3f
using namespace std;
int n,a[101],val,minn,sum,cnt,len,v[101];
bool dfs(int stick,int ll,int last){
     if(stick > cnt) return true;
	 if(ll == len)return dfs(stick + 1, 0, 0);
	 int fail = 0;
	 for(int i = last;i <= n; i++){
	 	  if(v[i] == 0&&a[i] + ll <= len&&fail != a[i]){//用別的木棍來拼 
	 	  	      v[i] = 1;
	 	  	     if(dfs(stick,ll + a[i],i))
	 	  	       return true;
				 v[i] = 0;
				 fail = a[i];
				 if(ll == 0||ll + a[i] == len)
			     return false;
		   }
	 }
	 return false;
}
int main(){
   while(cin>>n&&n){
      sum=0,val=0;
	 for(int i = 1;i <= n; i++){
	 	 scanf("%d",&a[i]);
	 	 sum += a[i];
	 	 val = max(a[i],val);
	  }
	 sort(a + 1,a + n + 1);
	 reverse(a + 1,a + n + 1);//整個陣列翻轉,實現從大到小排序 
	 for(len = val;len <= sum; len++){
	     if(sum % len) continue;//判斷列舉的長度是否合法 
		 cnt = sum/len;//原始木棍的根數
		 memset(v, 0 ,sizeof(v));
		 if(dfs(1, 0, 0)){
		 	 printf("%d\n",len);
		 	 break;
		 }	  
	  }	
   }
}