dfs之剪枝与优化

dfs之剪枝与优化

166. 数独

![在这里插入图片描述]( https://img-blog.csdnimg.cn/b9c27ce2887f43539c053610ab0a034d.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw==,size_20,color_FFFFFF,t_70,g_se,x_16)
样例输入:

4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
end

样例输出:

417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

代码模板:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 9;
int lowbit(int x){
	return x&(-x);
}
int col[N],row[N],cell[N][N];
char str[90];
int map[1<<N],ones[1<<N];

void init(){
    
    //将行和列和方格中得状态都设为可填状态 0为不可填 1为可填
	for(int i=0;i<N;i++) col[i]=row[i]=(1<<N)-1;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			cell[i][j]=(1<<N)-1;
}

int getones(int x,int y){
    //活得行列方格中所有1(可填)的交集
	return col[y]&row[x]&cell[x/3][y/3];
}

bool dfs(int cnt){
	if(!cnt) return true;
	
	//找到可填充元素最少的位置 -> 优化搜索顺序
	int minn=10,x,y;
	for(int i=0;i<9;i++)
		for(int j=0;j<9;j++)
			if(ones[getones(i,j)]<minn&&str[i*9+j]=='.'){
				minn=ones[getones(i,j)];
				x=i,y=j;
			}
	
	
	//开始填充数字
	for(int i=getones(x,y);i;i-=lowbit(i)){ //枚举所有1(可填)
		int t=map[lowbit(i)];  //获得低位1的十进制数 map获得低位1的位置
		row[x]-=(1<<t);
		col[y]-=(1<<t);
		cell[x/3][y/3]-=(1<<t);
		str[x*9+y]='1'+t;
		if(dfs(cnt-1)) return true;
		//回溯
		row[x]+=(1<<t);
		col[y]+=(1<<t);
		cell[x/3][y/3]+=(1<<t);
		str[x*9+y]='.';
	}		
			
	return false;
	 
}
int main(){
    //预处理某状态是第几位
	for(int i=0;i<N;i++) map[1<<i]=i;
	//预处理所有状态为1(可填)的个数
	for(int i=0;i<1<<N;i++){
		int s=0;
		for(int j=i;j;j-=lowbit(j)) s++;
		ones[i]=s;
	}
	
	while(cin>>str,str[0]!='e'){
		init(); //初始化
		int cnt=N*N;
		//将题目中已经填得数字初始化行列方格
		for(int i=0;i<9;i++)
			for(int j=0;j<9;j++)
				if(str[i*9+j]!='.'){
					int t=str[i*9+j]-'1';
					row[i]-=1<<t;
					col[j]-=1<<t;
					cell[i/3][j/3]-=1<<t;
					cnt--;  //填方格-1
				}
				
		
		dfs(cnt);
		
		cout<<str<<endl;
		
    }
    return 0;
}

 

167. 木棒

![在这里插入图片描述]( https://img-blog.csdnimg.cn/a70a41699cd346f185bc1900ce6f2edd.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw==,size_20,color_FFFFFF,t_70,g_se,x_16)
样例输入:

9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0

样例输出:

6
5

代码模板:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 64;

int n,sum,length;
int sticks[N];
bool st[N];

//u当前已经有多少根木棒 cur当前木棒的长度 start当前从哪根木棍开始枚举(木棍从大到小枚举)

bool dfs(int u,int cur,int start){
    //当前的木棒数量等于所有木棍的总和->当前木棒长度可行
	if(u*length==sum) return true;
	
	//当前的木棒长度可行 开始递归下一根木棒
	if(cur==length)  return dfs(u+1,0,0);
	
	//从大到小枚举木棍
	for(int i=start;i<n;i++){
		if(st[i]) continue;
		int l=sticks[i];
		if(cur+l>length) continue;
		
		st[i]=true;
		if(dfs(u,cur+l,i+1)) return true;
		st[i]=false;
		
		//如果是木棒的第一个木棍失败,则一定失败 
		if(!cur) return false;
		
		//如果是该木棒的最后一根木棒时失败时,说明前面的排法方案有问题,直接return
		if(cur+l==length) return false;
		
		// 跳过相同的木棒 
		int j=i;
		while(j<n && sticks[j]==l) j++;
		i=j-1;
	}
	
	return false;
}
int main(){
	while(cin>>n,n){
		sum=0,length=0;
		for(int i=0;i<n;i++){
			int l;
			cin>>l;
			sticks[i]=l;
			sum+=l;
			length=max(length,l);
		}
		
		//降序排序->优化搜索顺序->排除等效冗余 
		sort(sticks,sticks+n);
		reverse(sticks,sticks+n);
		
		memset(st,false,sizeof st);
		for(int i=0;i<n;i++)
			if(sticks[i]>50)
			    st[i]=true;
			    
		while(true){
		    //枚举的木棒的长度必须被sum整除
			if(sum%length==0&&dfs(0,0,0)){
				cout<<length<<endl;
				break;
			}
			length++;
		}
	}
	return 0;
}
 

 

168. 生日蛋糕

![在这里插入图片描述]( https://img-blog.csdnimg.cn/0f29b5868c1c4e78b10aa81480fe848b.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述]( https://img-blog.csdnimg.cn/6cb3aefaa7254bf0801aa66b1163b116.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw
,size_20,color_FFFFFF,t_70,g_se,x_16)

在这里插入图片描述
![在这里插入图片描述]( https://img-blog.csdnimg.cn/63908f8bd101496fb8d3533edd95a99b.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw==,size_20,color_FFFFFF,t_70,g_se,x_16)

![在这里插入图片描述]( https://img-blog.csdnimg.cn/ede778dab7594ed89b56df8ff7293ac1.png?x-oss-process=image/watermark ,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAUGFuc2XCtw==,size_20,color_FFFFFF,t_70,g_se,x_16)

思路:
1.首先看到题目知道暴搜,暴力枚举r和h,由于体积公式中r的影响最大所以先枚举r再枚举h,但是r和h有范围 最小:当前层数;最大 (1)剩下的体积根据公式当高等于1时候r取得最大值 (2) 下一层的半径+1。
2.当剩下的体积小于可安排的最小体积,return;当前表面积加上可安排的最小表面积大于ans,return;
3.根据表面积和体积公式求得关系剪枝:当前已经安排表面积得加上(...大于)ans,return(最恶心且最重要得一环)
样例输入:

100
2

样例输出:

68

代码模板:

#include <bits/stdc++.h>

using namespace std;

const int M = 30;

int n, m, ans = 0x3f3f3f3f;
int minv[M], mins[M], R[M], H[M];
//自底向上递归
//v当前处理的体积和 s当前处理的面积和
void dfs(int dep, int v, int s)
{
    if (v + minv[dep] > n) return;
    if (s + mins[dep] >= ans) return; // 剪枝(4)
    if (s + 2 * (n - v) / R[dep + 1] >= ans) return; // 剪枝(5)

    if(dep == 0)
    {
        if (v == n) ans = min(ans, s);
        return;
    }

    // 剪枝(2)(3)
    for (int r = min(R[dep + 1] - 1, (int)sqrt(n - v)); r >= dep; r -- )
        for (int h = min(H[dep + 1] - 1, (n - v) / r / r); h >= dep; h -- )
        {
            R[dep] = r, H[dep] = h;
            dfs(dep - 1, v + r * r * h, s + 2 * r * h + (dep == m ? r * r : 0));
        }
}

int main()
{
    scanf("%d %d", &n, &m);

    for (int i = 1; i <= m; i ++ )
    {
        minv[i] = minv[i - 1] + i * i * i;
        mins[i] = mins[i - 1] + 2 * i * i; 
    }

    R[m + 1] = H[m + 1] = 0x3f3f3f3f;

    dfs(m, 0, 0); // 剪枝(1)

    if (ans == 0x3f3f3f3f) puts("0");
    else printf("%d\n", ans);

    return 0;
}


 

posted @ 2022-03-22 14:48  panse·  阅读(15)  评论(0)    收藏  举报