暴力法往往比较低效,把时间浪费在很多不必要的计算上。
剪枝是一个比喻:把不会产生答案的,或不必要的枝条“剪掉”。而剪枝的关键在于剪枝的判断:剪什么枝、在哪里减。剪枝是搜索常用的优化手段,常常能把指数级的复杂度,优化到近似多项式的复杂度。
- BFS的主要剪枝技术是判重,如果搜索到某一层时,出现重复的状态,就剪枝。
- DFS的剪枝技术较多,有可行性剪枝、最优性剪枝、搜索顺序剪枝、排除等效冗余、记忆化搜索等等:
- 可行性剪枝:对当前状态进行检查,如果当前条件不合法就不再继续,直接返回
- 搜索顺序剪枝:搜索树有多个层次和分支,不同的搜索顺序会产生不同的搜索树形态,复杂度也相差很大。
- 最优性剪枝:在最优化问题的搜索过程中,如果当前花费的代价已超过前面搜索到的最优解,那么本次搜索已经没有继续进行下去的意义,此时停止对当前分支的搜索进行回溯。
- 排除等效冗余:搜索的不同分支,最后的结果是一样的,那么只搜一个分支就够了。
- 记忆化搜索:在递归的过程中,有许多分支被反复计算,会大大降低算法的执行效率。用记忆化搜索,将已经计算出来的结果保存起来,以后需要用到的时候直接取出结果,避免重复运算,从而提高了算法的效率。记忆化搜索一般在DP中。
第一题 四平方和
样例输入
12
样例输出
0 2 2 2
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int i = 0, j = 0, k = 0, l = 0;
int x = scanner.nextInt();
for (i = 0; x >= i * i; i++) {
for (j = i; x >= i * i + j * j; j++) {
for (k = j; x >= i * i + j * j + k * k; k++) {
for (l = k; x >= i * i + j * j + k * k + l * l; l++) {
if (i * i + j * j + k * k + l * l == x) {
System.out.println(i + " " + j + " " + k + " " + l);
return;
}
}
}
}
}
}
}
第二题 剪格子
输出描述
在所有解中,包含左上角的分割区可能包含的最小的格子数目。
样例输入
3 3
10 1 52
20 30 1
1 2 3
样例输出
3
import java.util.Scanner;
public class Main {
public static int[][] map;
public static int[][] visited;
public static int m;
public static int n;
public static int sum = 0;
public static int min = 999999;
public static int[][] direction = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
m = scanner.nextInt();
n = scanner.nextInt();
map = new int[1000][1000];
visited = new int[1000][1000];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
map[i][j] = scanner.nextInt();
visited[i][j] = 0;
sum = sum + map[i][j];
}
}
dfs(0, 0, 0, 1);
System.out.println(min);
}
public static void dfs(int x, int y, int temp, int k) {
temp = temp + map[x][y];
if (temp * 2 > sum) {
return;
}
if (temp * 2 == sum) {
if (k < min) {
min = k;
}
return;
}
visited[x][y] = 1;
for (int i = 0; i < direction.length; i++) {
int x1 = x + direction[i][0];
int y1 = y + direction[i][1];
if (x1 >= m || y1 >= n) {
continue;
}
if (x1 >= 0 && x1 < n && y1 >= 0 && y1 < m && visited[x1][y1] == 0) {
dfs(x1, y1, temp, k+1);
}
}
visited[x][y] = 0;
}
}
写的贼慢,太菜了。
第三题 路径之谜
样例输入
4
2 4 3 4
4 3 3 3
样例输出
0 4 5 1 2 3 7 11 10 9 13 14 15
import java.util.Scanner;
import java.util.Stack;
public class Main {
private static int n = 0;
private static int[] north;
private static int[] west;
private static int[][] map;
private static int[][] direction = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
private static int[][] visited;
private static Stack<Node> stack = new Stack<Node>();
private static int temp = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
north = new int[n];
west = new int[n];
map = new int[n][n];
visited = new int[n][n];
for (int i = 0; i < n; i++) {
north[i] = scanner.nextInt();
}
for (int i = 0; i < n; i++) {
west[i] = scanner.nextInt();
}
int temp = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
map[i][j] = temp;
visited[i][j] = 0;
temp++;
}
}
dfs(0, 0);
}
private static void dfs(int x, int y) {
west[x]--;
north[y]--;
visited[x][y] = 1;
int flag = 0;
if (temp == 1) {
return;
}
if (west[x] < 0 || north[y] < 0) {
west[x]++;
north[y]++;
visited[x][y] = 0;
return;
}
if (x == n - 1 && y == n - 1) {
for (int i = 0; i < n; i++) {
if (west[i] != 0 || north[i] != 0) {
flag = 1;
break;
}
}
if (flag != 1) {
temp = 1;
Node node = new Node(x, y, map[x][y]);
stack.push(node);
int[] a = new int[stack.size()];
for (int i = stack.size() - 1; !stack.isEmpty(); i--) {
a[i] = stack.pop().value;
}
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
return;
}
}
Node node = new Node(x, y, map[x][y]);
stack.push(node);
for (int i = 0; i < direction.length; i++) {
int nx = x + direction[i][0];
int ny = y + direction[i][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < n && visited[nx][ny] == 0) {
dfs(nx, ny);
}
}
if (!stack.isEmpty()) {
stack.pop();
}
visited[x][y] = 0;
west[x]++;
north[y]++;
}
}
class Node {
int x;
int y;
int value;
Node(int x, int y, int value) {
this.x = x;
this.y = y;
this.value = value;
}
}
剪枝前深搜722次 剪枝后深搜54次
第四题 分考场
样例输入
5
8
1 2
1 3
1 4
2 3
2 4
2 5
3 4
4 5
样例输出
import java.util.*;
public class Main {
private static int n;
private static int m;
private static int ans = 10000;
private static int[][] relation;
private static Map<Integer, List<Integer>> map = new HashMap<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
relation = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
relation[i][j] = 0;
}
}
for (int i = 0; i < m; i++) {
int a1 = scanner.nextInt();
int a2 = scanner.nextInt();
relation[a1][a2] = 1;
relation[a2][a1] = 1;
}
List<Integer> list = new ArrayList<>();
list.add(1);
map.put(0, list);
dfs(1);
System.out.println(ans);
}
public static boolean checked(int i, int k) {
for (int j = 0; j < map.get(i).size(); j++) {
if (relation[k][map.get(i).get(j)] == 1 || relation[map.get(i).get(j)][k] == 1) {
return false;
}
}
return true;
}
public static void dfs(int k) {
System.out.println("DFS");
if (k == n + 1) {
ans = Math.min(ans, map.size());
return;
}
if (map.size() >= ans) {
return;
}
for (int i = 0; i < map.size(); i++) {//对每一个考场进行遍历
if (checked(i, k)) {//对第i个考场进行检查
map.get(i).add(k);
dfs(k + 1);
map.get(i).remove(map.get(i).size() - 1);//将集合最后的元素移除
}
/*int flag = 0;
List<Integer> list = map.get(i);
for (int j = 0; j < list.size(); j++) {//对每一个考场中的每个人进行遍历
int temp = list.get(j);
if (relation[k][temp] == 1 || relation[temp][k] == 1) {//有关系
flag = 1;
break;
}
}
if (flag == 0) {
list.add(k);//第i个考场里没有任何一个人和k有关系,将k加入到第i号考场
dfs(k + 1);
list.remove(list.size() - 1);
break;//对每个考场的遍历可以结束了
}
if (i == map.size()) {//到了最后一次循环 则创建新的考场
List<Integer> list1 = new ArrayList<>();
list1.add(k);//将第k个学生加入集合
map.put(map.size(), list1);//将新创建的考场加入到考场里
dfs(k + 1);
map.remove(map.size() - 1);
}*/
}
List<Integer> list = new ArrayList<>();
list.add(k);
map.put(map.size(), list);
dfs(k + 1);
map.remove(map.size() - 1);
}
}
纯纯的鬼物题
对于第k个学生来说,即使能加入到某个教室里,也要在每个dfs()的最后。为他新建一个考场去尝试。想明白这一点就很容易写。
第五题 四阶幻方
1 2 15 16
12 14 3 5
13 7 10 4
8 11 6 9
以及:
1 12 13 8
2 14 7 11
15 3 10 6
16 5 4 9
就可以算为两种不同的方案。
public class Main {
private static int[][] map;
private static int count = 0;
private static int[] a = {9999, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
public static void main(String[] args) {
map = new int[5][5];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
map[i][j] = 0;
}
}
map[1][1] = 1;
dfs(2, 16);
System.out.println(count);
}
private static void dfs(int s, int t) {
//System.out.println("DFS");
if (s == 5 && map[1][1] + map[1][2] + map[1][3] + map[1][4] != 34) {
//System.out.println(map[1][1] + " " + map[1][2] + " " + map[1][3] + " " + map[1][4]);
return;
}
if (map[1][1] + map[1][2] + map[1][3] + map[1][4] == 34) {
//System.out.println(map[1][1] + " " + map[1][2] + " " + map[1][3] + " " + map[1][4]);
}
if (s == 9 && map[2][1] + map[2][2] + map[2][3] + map[2][4] != 34) {
return;
}
if (s == 13 && map[3][1] + map[3][2] + map[3][3] + map[3][4] != 34) {
return;
}
if (s == 14 && map[1][1] + map[2][1] + map[3][1] + map[4][1] != 34) {
return;
}
if (s == 14 && map[1][4] + map[2][3] + map[3][2] + map[4][1] != 34) {
return;
}
if (s == 15 && map[1][2] + map[2][2] + map[3][2] + map[4][2] != 34) {
return;
}
if (s == 16 && map[1][3] + map[2][3] + map[3][3] + map[4][3] != 34) {
return;
}
if (s == 17 && map[1][4] + map[2][4] + map[3][4] + map[4][4] == 34 && map[1][1] + map[2][2] + map[3][3] + map[4][4] == 34 && map[4][1] + map[4][2] + map[4][3] + map[4][4] == 34) {
count++;
return;
}
for (int i = s; i <= t; i++) {
swap(i, s);
for (int j = 2; j <= 16; j++) {
if (j % 4 != 0) {
map[j / 4 + 1][j % 4] = a[j];//赋值
} else {
map[j / 4][4] = a[j];
}
}
dfs(s + 1, t);
swap(s, i);
}
}
private static void swap(int i, int s) {
int temp;
temp = a[i];
a[i] = a[s];
a[s] = temp;
}
}
答案为416种 对16个数进行全排列 跑的很慢 不知道是不是哪块的剪枝出了问题