Loading

深搜的剪枝技巧

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;
		 }	  
	  }	
   }
}

10021. 「一本通 1.3 例 4」Addition Chains

mind:

关于迭代加深

用BFS思想写DFS,逐层的搜,如果没有找到可行解,在搜下一层,直到搜到可行解

剪枝:

需要的序列是单调递增的,枚举 i , j 得到k;因此如果发现ans[i]+ans[j]>n,那么如果 i 不变,j 往后增一定也大于n;

强剪枝:

ans[i]<=ans[i-1]*2

也就是说后一项最多是前一项的2倍,如果当前已知ans[k]然后迭代加深的最大深度为maxd,则后面最多还有maxd - k项,也就是说ans[k]*2^k<n则在maxd次之内一定找不到答案

node

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int M = 10010;
int n,ans[M],maxd;
bool dfs(int k){
	if(k > maxd) return ans[k - 1] == n;
	if(ans[k - 1] * (1 << (maxd - k + 1)) < n)return 0;
	for (int i = 0;i < k; i++){
	   for (int j = i;j < k; j++){
	   	   int t = ans[i] + ans[j];
	   	   if(t <= ans[k-1]) continue;
	   	   if(t > n) break;
	   	   ans[k] = t; 
	   	   if(dfs(k + 1)) return 1;
	   }
	}
	return 0;
}
signed main(){
    ans[0] = 1;
     while (scanf("%lld",&n)&&n){
    	printf("1");
    	for(maxd = 0;;maxd++){
    		 if(dfs(1)){
    		 	for(int i = 1;i <= maxd; i++) printf(" %lld",ans[i]);
				printf("\n");
				break;
			 }
		}
	}
}

10249. 「一本通 1.3 例 5」weight

mind

这个题虽然不难但是收获很大,首尾dfs,跑到中间时判断合不合法,不和法两边回溯;

node

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1010000;
const int M = 1000100;
int n,m,a[N],bok[M];
int read(){
	int x = 0,f = 1;char c = getchar();
	while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
	return x*f;
}
bool flag;
int ansl[N],ansr[N];
void dfs(int pos,int l,int r){
    if(pos == n*2 + 1){
    	flag = true;
    	return ;
	}
	if(r < l && ansl[l] + ansr[l + 1] != a[n * 2])//搜到中间,但不符合要求 
		return;
		  
	if(bok[a[pos] - ansl[l]]&& l < n){
		ansl[l + 1] = a[pos];
		dfs(pos + 1,l + 1, r);
		if(flag) return ;
	} 
	if(bok[a[pos] - ansr[r]]&&r > 1){
		ansr[r - 1] = a[pos];
		dfs(pos + 1,l, r -1);
		if(flag) return ;
	}
	return ;
}
int main(){
   n = read();
   for(int i = 1;i <= n*2; i++){
   	  a[i] = read();
   }
   m = read();
   for(int i = 1,x;i <= m; i++){
   	  bok[x = read()] = 1;
   }
   sort(a + 1,a + n * 2 + 1);
   dfs(1, 0, n + 1);
   for(int i = 1;i < n; i++){
   	  printf("%d ",ansl[i]- ansl[i-1]);
   }
   printf("%d",ansl[n] - ansl[n-1]);
}

10023. 「一本通 1.3 练习 2」平板涂色

mind:

比较简单的深搜题,枚举每一个方块,看看它是否能被涂(上面是否已经布满已经涂好的颜色)

如果这个方块颜色与枚举时的颜色不同,就更新cnt++

边界:

if(tot >= ans) return;
if(f == n) ans = tot;

node

/*
work by:Ariel_
*/
#include <iostream>
#include <cstdio>
using namespace std;
int read(){
	int x = 0,f = 1;char c = getchar();
	while(c < '0'||c > '9'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
	return x*f;
}
struct block{
   int lx,ly,rx,ry,c;
}a[20];
bool vis[20];
int n,ans;
bool check(int k){
	for(int i = 1;i <= n; i++){
		if(!vis[i]&&a[i].ly == a[k].ry&&((a[i].lx>=a[k].lx&&a[i].lx<=a[k].rx)||(a[i].rx>=a[k].lx&&a[k].rx>=a[i].rx)))
		return 0;
	}
	return 1;
}
inline void dfs(int tot,int f,int c){
	if(tot >= ans) return;
	if(f == n) ans = tot;
	for(int i = 1;i <= n; i++){
		if(!vis[i] && check(i)){
			vis[i] = 1;
	        dfs(tot + (a[i].c - c != 0), f + 1, a[i].c);
			 vis[i] = 0;
		}
	}
	return;
}
int main(){
   n = read();
   ans = n;
   for(int i = 1;i <= n; i++){
	   a[i].ly= read();a[i].lx = read();a[i].ry = read(); a[i].rx = read();a[i].c = read();
   }
   dfs(0,0,-1);
   printf("%d",ans);
   
}
posted @ 2020-12-26 22:24  Dita  阅读(113)  评论(0编辑  收藏  举报