寄蜉蝣于天地,渺沧海之一粟。哀吾生之须臾,羡长江之无穷。
—苏轼
说到暴力,我们大家最先想到一定是枚举,但是枚举真的是一门技术,怎么样把所有情况一个不落下的枚举出来是比较难的,所以我们这节课给大家讲解一下枚举法。
知识点
枚举法
枚举算法的思想:
将问题的所有可能成为答案的解一一列举,然后根据问题所给出的条件判断此解是否合适,如果合适就保留,反之则舍弃。
枚举算法解题的基本思路:
枚举算法的一般步骤:
枚举法也是有很多技巧和方法的,这节课我们将从如下几种方法为大家进行讲解。
简单型枚举
简单型枚举就是可以通过简单的 for 循环嵌套就可以解决的问题。我们之前的课讲的题目都算是简单型枚举的范畴,所以简单型枚举是比较简单,也是大家接触最多的一种枚举方式。
这种枚举方式没有特定的固定枚举方式,而且都比较简单,按照题目的要求进行设计代码即可完成解题。
我们用一个题复习一下。
题目描述:
众所周知在扑克牌中,有一个老掉牙的游戏叫做24点,选取4张牌进行加减乘除,看是否能得出24这个答案。
现在小蓝同学发明了一个新游戏,他从扑克牌中依次抽出6张牌,注意不是一次抽出,进行计算,看是否能够组成 42 点,满足输出YES,反之输出 NO。
最先抽出来的牌作为第一个操作数,抽出牌做第二个操作数,运算结果在当作第一个操作数,继续进行操作。
除不尽的情况保留整数。
请设计一个程序对该问题进行解答。
样例:
输入:
K A Q 6 2 3
输出:
YES
对于上面的样例我们进行了如下计算;
1. K*A=K 即 13*1=13
2. 13/12=1 保留整数
3. 1+6=7
4. 7*2=14
5. 14*3=42
运行限制:
最大运行时间:1s
最大运行内存: 128M
代码:
public class _1024点问题 {
static int[] a = new int[10];
static Vector<Vector<Integer>> ans = new Vector<Vector<Integer>>();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
for (int i=0; i<6; ++i) {
String c;
c = in.next();
if (c.charAt(0) == 'A') {
a[i] = 1;
} else if (c.charAt(0) == 'J') {
a[i] = 11;
} else if (c.charAt(0) == 'Q') {
a[i] = 12;
} else if (c.charAt(0) == 'K') {
a[i] = 13;
} else {
a[i] = (c.charAt(0) - '0');
}
}
ans.addElement(new Vector<Integer>()); // 构造二维数组
ans.get(0).addElement(a[0]); // v[0][0] = a[0]
for (int i=1; i<=5; ++i) {
ans.addElement(new Vector<Integer>());
for(int j=0; j<ans.get(i-1).size(); ++j) {
// 枚举符号
ans.get(i).addElement(ans.get(i-1).get(j) + a[i]);
ans.get(i).addElement(ans.get(i-1).get(j) - a[i]);
ans.get(i).addElement(ans.get(i-1).get(j) * a[i]);
ans.get(i).addElement(ans.get(i-1).get(j) / a[i]);
}
}
int flag = 0;
for (int i=0; i<ans.get(5).size(); ++i) {
if (ans.get(5).get(i) == 42) {
flag = 1;
break;
}
}
if (flag == 1) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}
}
组合型枚举
排列组合相信大家都学习过,组合型枚举就是让你在 n 个中,随机选出 m 个,问你有多少种方案,而且每一种方案选择了哪 m 个,这就是组合型枚举。
即组合型枚举就是寻找 c_{n}^m 问题。
组合型枚举有固定的流程,即有着固定的算法模板,这个需要大家去记忆一下。
public class _m01 {
static int n; //总共n个数
static int m; //选m个数
static Vector<Integer> chosen = new Vector<Integer>();
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
m = in.nextInt();
calc(1); // 从1开始选
}
static <object> void calc (int x) {
// 剪枝 1.超过所选数目 或 2.选不够 m 个
if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
return;
}
// 选够了m个数输出
if (x == n + 1) {
String ansTem = "";
for (int i=0; i<chosen.size(); ++i) {
System.out.print(chosen.get(i) + " ");
}
System.out.println("");
return;
}
// 选x
calc(x + 1);
chosen.addElement(x);
// 不选x
calc(x + 1);
chosen.remove((Object)x);
}
}
题目描述:
小A的学校,蓝桥杯的参赛名额非常有限,只有m个名额,但是共有n个人报名,其中m<=n。作为老师非常苦恼,他不知道该让谁去,他在寻求一个绝对公平的方式。于是他准备让大家抽签决定,即m个签是去,剩下的是不去。
小A非常想弄明白最后的抽签结果是什么样子的,到底有多少种结果。
请设计一个程序帮助小A。最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。
第一行 输入 N M
第二行 到 第 N+1 行 共输入 N 个人名
每种情况输出 M 个人名,空格隔开。
样例:
输入:
3 2
xiaowang
xiaoA
xiaoli
输出:
xiaowang xiaoA
xiaowang xiaoli
xiaoA xiaoli
运行限制:
1. 最大运行时间:1s
2. 最大运行内存:128M
代码:
public class _11公平抽签 {
static int n;
static int m;
static Vector<String> name = new Vector<String>();
static Vector<String> ans = new Vector<String>();
static Vector<Integer> chosen = new Vector<Integer>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for (int i=0; i<n; ++i) {
String s;
s = sc.next();
name.addElement(s);
}
calc(1);
for(int i=ans.size()-1; i>=0; --i) {
System.out.println(ans.get(i));
}
}
static void calc(int x) {
if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
return;
}
if (x == n + 1) {
String ansTem = "";
for (int i=0; i<chosen.size(); ++i) {
ansTem += name.get(chosen.get(i) - 1) + " ";
}
ans.addElement(ansTem);
return;
}
calc(x + 1);
chosen.addElement(x);
calc(x +1);
chosen.removeElement((Object)x);
}
}
排列型枚举
上面说过,组合型枚举就是让你在 n 个中,随机选出 m 个 ,问你有多少种方案,而且每一种方案选择了哪 m 个,这就是组合型枚举。
而排列型枚举相对组合型枚举就简单了一点,就是 n 个的全排列,即从 n 个中选取 n 个但是关心内部的顺序。
相比较组合只关心有多少个集合,而排列是关心集合内的排列方式。即排列型枚举就是寻找 A_{n}^n 问题。不少同学问我 20 够不够,排列问题是阶乘阶的时间复杂度,如果超过这个复杂度,那么这个题也就不用做了,算不出来。
而且排列型枚举也是有着比较成熟的模板需要大家进行记忆。
public class _m02 {
static int n;
static int[] order = new int[20];
static boolean[] chosen = new boolean[20];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
calc(1);
}
static <object> void calc(int x) {
if (x == n+1) { // 选够 m 个数输出
String ansTem = "";
for (int i=1; i<=n; ++i) {
System.out.print(order[i] + " ");
}
System.out.println();
return;
}
for (int i=1; i<=n; ++i) {
if (chosen[i]) {
continue;
}
// 选x
order[x] = i;
chosen[i] = true;
calc(x + 1);
// 不选x
chosen[i] = false;
order[x] = 0;
}
}
}
题目描述:
小 A 的学校,老师好不容易解决了蓝桥杯的报名问题,现在老师又犯愁了。现在有 N 位同学参加比赛,但是老师想给他们排座位,但是排列方式太多了。老师非常想弄明白最后的排座次的结果是什么样子的,到底有多少种结果。
请设计一个程序帮助老师。
最后输出各种情况的人名即可,一行一种情况,每种情况的名字按照报名即输入顺序排序。
第一行 输入 N;
第二行 到 第N+1 行 共输入 N 个人名。
由于小 A 学校承办能力实在有限,所以其中 N 小于等于 10 人。
样例:
输入:
3
xiaowang
xiaoA
xiaoli
输出:
xiaowang xiaoA xiaoli
xiaowang xiaoli xiaoA
xiaoA xiaowang xiaoli
xiaoA xiaoli xiaowang
xiaoli xiaowang xiaoA
xiaoli xiaoA xiaowang
运行限制:
1. 最大运行时间:1s
2. 最大运行内存:128M
代码:
public class _12座次问题 {
static int n;
static int[] order = new int[20];
static boolean[] chosen = new boolean[20];
static Vector<String> name = new Vector<String>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for (int i=0; i<n; ++i) {
String s;
s = sc.next();
name.addElement(s);
}
calc(1);
}
static <object> void calc(int x) {
if (x == n+1) {
String ansTem = "";
for (int i=1; i<=n; ++i) {
ansTem += name.get(order[i] - 1)+ " ";
}
System.out.println(ansTem);
return;
}
for (int i=1; i<=n; ++i) {
if (chosen[i]) {
continue;
}
order[x] = i;
chosen[i] = true;
calc(x + 1);
order[x] = 0;
chosen[i] = false;
}
}
}