DFS之剪枝与优化-小猫爬山、 数独、 木棒

小猫爬山

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2......CN。

当然,每辆缆车上的小猫的重量之和不能超过 W。

每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式

第 1 行:包含两个用空格隔开的整数,N 和 W。

第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。

输出格式

输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围

1≤N≤18,

1≤Ci≤W≤108

输入样例:
复制代码
5 1996
1
2
1994
12
29
输出样例:
复制代码
2

代码1(二分 + 可行性判断 DFS

复制代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.StringTokenizer;



public class Main {
	
	static int N=10010,id=1,id1=1,n,m,w;
	

	
	static Integer a[]=new Integer[20];
	static int group[]=new int[20];
	static long capacity[]=new long[20];//每艘船的剩余容量
	//static boolean f[][]=new boolean[20][20];
	
	static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
	
	 public static void main(String[] args) throws IOException {
	    
	     
		 StringTokenizer st=new StringTokenizer(br.readLine());
		 n=Integer.parseInt(st.nextToken());
		 w=Integer.parseInt(st.nextToken());
		 Arrays.fill(capacity, w);
		 
		 for (int i = 0; i < n; i++) {
			a[i]=Integer.parseInt(br.readLine().trim());
		 }
		 Arrays.sort(a, 0, n, Collections.reverseOrder());//从大到小枚举 可以大大减少分支数量
		 int l=1,r=18;
		 while(l<r){
			 int mid=(l+r)>>1;
		  	 if(dfs(mid,0)){
		  		 r=mid;
		  	 }else{
		  		 l=mid+1;
		  	 }
		 }
		 bw.write(l+"");
		
		bw.flush();
		bw.close();
		bw.close();
	 }
   
	 static boolean dfs(int tot,int k){
		 if(k==n){
			 return true;
		 }
		 boolean res=false;
		 for (int i = 1; i <= tot; i++) {//枚举那艘船可以放下a[k]
			if(res)break;
			if(capacity[i]>=a[k]){
				capacity[i]-=a[k];
				if(dfs(tot, k+1)){
				  res=true;
				}
				capacity[i]+=a[k];
			}
			 if (capacity[i] == w) break; // w 是初始容量  注意要加这一行剪枝   如果说当前的这个物品连最大的容量也放不下,那就没有必要再去枚举其他的船了。
		 }
		 return res;
	 }
	 
	
}

代码2(单次 DFS + 最优性剪枝

复制代码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.StringTokenizer;



public class Main {
	
	static int N=10010,id=1,id1=1,n,m,w,t,res=Integer.MAX_VALUE;
	

	
	static Integer a[]=new Integer[20];
	static long capacity[]=new long[20];//每艘船的剩余容量
	//static boolean f[][]=new boolean[20][20];
	
	static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
	
	 public static void main(String[] args) throws IOException {
	    
	     
		 StringTokenizer st=new StringTokenizer(br.readLine());
		 n=Integer.parseInt(st.nextToken());
		 w=Integer.parseInt(st.nextToken());
		 
		 
		 for (int i = 0; i < n; i++) {
			a[i]=Integer.parseInt(br.readLine().trim());
		 }
		 Arrays.sort(a, 0, n, Collections.reverseOrder());//倒序遍历
		 dfs(1,0);
		bw.write(res+"");
		
		bw.flush();
		bw.close();
		bw.close();
	 }
   
	 static void dfs(int k,int u){//当前已经用了k辆车 现在正在考虑a[k]物品
		 if(k>res)return;
		 if(u==n){//能到这 一定是最终的答案 因为有好多剪枝
			 res=k;
			 return;
		 }
		 //可以选择放到现有的k辆车的某一个中 
		 for (int i = 1; i <= k; i++) {
			if(capacity[i]+a[u]<=w){//可以装得下
				capacity[i]+=a[u];
				dfs(k, u+1);
				capacity[i]-=a[u];//恢复现场
			}
		 }
		 //也可以直接开一个新车
		 capacity[++k]+=a[u];
		 dfs(k, u+1);
		 capacity[k--]-=a[u];
		 
	 }
}

数独

数独 是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得数独中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。

请编写一个程序填写数独。

输入格式

输入包含多组测试用例。

每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。

每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。

您可以假设输入中的每个谜题都只有一个解决方案。

文件结尾处为包含单词 end 的单行,表示输入结束。

输出格式

每个测试用例,输出一行数据,代表填充完全后的数独。

输入样例:
复制代码
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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;


public class Main {
	
	static int N=10010,id=1,id1=1,n,m,w,t,res=Integer.MAX_VALUE;
	
	static char c[]=new char[82];
	//static boolean f[]=new boolean[82];
	static boolean ge[][]=new boolean[9][9];//ge[i][j]表示将9×9的矩阵图看成9个3×3的矩阵之后第i个矩阵存不存在数字j
	static boolean row[][]=new boolean[9][9];//row[i][j]表示第i行有没有数字j
	static boolean col[][]=new boolean[9][9];//col[i][j]表示第i列有没有数字j
	
	static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
	
	 public static void main(String[] args) throws IOException {
	    
	     
//		 StringTokenizer st=new StringTokenizer(br.readLine());
//		 n=Integer.parseInt(st.nextToken());
//		 w=Integer.parseInt(st.nextToken());
		 
		 String line;
		 
		 while(!(line=br.readLine()).equals("end")){
			 c=line.toCharArray();
			 for (int i = 0; i < 9; i++) {
				Arrays.fill(ge[i], false);
				Arrays.fill(row[i], false);
				Arrays.fill(col[i], false);
			 }
			 for (int i = 0; i < 81; i++) {
				if(c[i]!='.'){
					c[i]=(char)(c[i]-1);
					int num=c[i]-'0';
					int x=i/9,y=i%9;
					
					int gx=x/3,gy=y/3;
					ge[gx*3+gy][num]=true;
					
					row[x][num]=true;
					col[y][num]=true;
				}
			 }
			 dfs();
		 }
		 
		 
		 
		bw.flush();
		bw.close();
		bw.close();
	 }
   
	 
	 static boolean dfs() throws IOException{
		 
		 int k=-1,mincnt=Integer.MAX_VALUE;//找到一个最受限的位置
		 
		 for (int j = 0; j < 81; j++) {
			 if(c[j]!='.')continue;
			 int x=j/9,y=j%9;
			 int cnt=0;
			 for (int i = 0; i < 9; i++) {
				 if(!row[x][i] && !col[y][i] && !ge[x/3*3+y/3][i]){
					 cnt++;
				 }
			}
			if(cnt<mincnt){
				k=j;
				mincnt=cnt;			
		        if (cnt == 1) break;         // 唯一候选,就是最佳位置,无需再找
			}
			
		 }
		 if(k==-1){
			 StringBuilder stringBuilder=new StringBuilder();
			 for (int i = 0; i < 81; i++) {
				stringBuilder.append((char)(c[i]+1));
			 }
			 bw.write(stringBuilder.toString()+"\n");
			 return true;
		 }
		 
		 int x=k/9,y=k%9;
		 
		 for (int i = 0; i < 9; i++) {
			
			if(!row[x][i] && !col[y][i] && !ge[x/3*3+y/3][i]){
				row[x][i]=true;col[y][i]=true;ge[x/3*3+y/3][i]=true;
				c[k]=(char)(i+'0');
				
				if(dfs()){
					return true;
				}
				
				row[x][i]=false;col[y][i]=false;ge[x/3*3+y/3][i]=false;
				c[k]='.';
				
			}
		 }
		 return false;
	 }
	 
}

木棒

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

输入格式

输入包含多组数据,每组数据包括两行。

第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。

输出格式

为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围

数据保证每一节木棍的长度均不大于 50。

输入样例:
复制代码
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
复制代码
6
5

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.StringTokenizer;


public class Main {
	
	static int N=110,id=1,id1=1,n,m;
	
	static Integer a[]=new Integer[N];
	static int group[]=new int[N];
	static boolean f[]=new boolean[N];
	
	
	static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
	
	 public static void main(String[] args) throws IOException {
	    
	     
//		 StringTokenizer st=new StringTokenizer(br.readLine());
//		 n=Integer.parseInt(st.nextToken());
//		 w=Integer.parseInt(st.nextToken());
		 
		 String line;
		 
		 while(!(line=br.readLine()).equals("0")){
			 n=Integer.parseInt(line);

			 StringTokenizer st=new StringTokenizer(br.readLine());
			 int l=Integer.MIN_VALUE;
			 int sum=0;
			 for (int i = 0; i < n; i++) {
				a[i]=Integer.parseInt(st.nextToken());
				l=Math.max(a[i], l);
				sum+=a[i];
			 }
			 Arrays.sort(a,0,n,Collections.reverseOrder());
			 
			
			 for (int i = l; i <= sum; i++) {//依次枚举最小可能长度
				if(sum%i==0){
					Arrays.fill(f, false);
					if(dfs(0,i,0,i,sum/i)){
						bw.write(i+"\n");
						break;
					}
				}
			}
		 }
		 
		 
		 
		bw.flush();
		br.close();
		bw.close();
	 }
   
	 
	 static boolean dfs(int k,int capbility,int start,int len,int groupnum) throws IOException{
		 //当前在拼k组   当前正在拼的k组还需要多少才能拼出
		 //Start保证 在拼一整个木块的时候,按照递增的顺序去选中 这样就可以防止很多重复的一些组合。
		 //原来整个木块儿的长度以及需要拼成的组数
		 if(k==groupnum){
			 return true;
		 }
		 for (int i = start; i < n; i++) {
			if(!f[i]){//当前木棍没有被用过
				f[i]=true;
				if(capbility>a[i]){					
					if(dfs(k, capbility-a[i],i+1, len, groupnum)){
						return true;
					}					
				}else if(capbility==a[i]){
					if(dfs(k+1, len,0, len, groupnum)){
						return true;
					}
					
				}
				f[i]=false;
				 //剪枝1
				//a[i] 是当前全局最长的未用木棍,它迟早要做某个空组的开头
	            if (capbility == len) return false;//start==0和capbility == len一定是同时发生的
	            //剪枝2
	            //假设存在某个成功的拼法,其中当前组不是用 a[i] 填满的,而是用几根更短的木棍
	            //(比如 x + y)凑出   
	            //capbility,然后把 a[i] 留到后面的某一组使用。那么结果一定也是false
	            if (capbility == a[i]) return false;
				
				while(i+1<n && a[i+1]==a[i])i++;//跳过与我相同的情况 因为一定不会成功
			}
		}
		return false;
	 }
	 
}
相关推荐
Navigator_Z1 小时前
LeetCode //C - 1187. Make Array Strictly Increasing
c语言·算法·leetcode
龍德明宇2 小时前
如何理解大语言模型的负主体性-龍德明宇
人工智能·算法·大语言模型llm·负主体性·ai存在论
白狐_7982 小时前
408数据结构第6章:迪杰斯特拉(Dijkstra)算法——真题精讲
数据结构·算法
皓月斯语3 小时前
P2858 [USACO06FEB] Treats for the Cows G/S
数据结构·c++·算法·动态规划
旖旎夜光3 小时前
LeetCode 397:整数替换(贪心问题) —— 题解
数据结构·c++·算法·leetcode·贪心算法
奈斯先生Vector3 小时前
2026 开发者效能革命:基于创源AIGC 与开源生态的工程化实践、安全沙箱与代码智能体协同
人工智能·算法·架构·prompt·aigc
依然鸣3 小时前
PTA团体程序设计天梯赛L2真题讲解L2-037-040
c++·经验分享·学习·算法·蓝桥杯·深度优先·pat考试
Sagittarius_A*3 小时前
哈希与认证基础(一):哈希函数的安全目标:原像、第二原像与碰撞
算法·安全·信息安全·密码学·哈希算法
坚持编程的菜鸟4 小时前
模拟实现strncat
c语言·算法·模拟实现strncat