第七届蓝桥杯:剪邮票
题目
题目:剪邮票
如【图1.jpg】, 有12张连在一起的12生肖的邮票。
现在你要从中剪下5张来,要求必须是连着的。
(仅仅连接一个角不算相连)
比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。请你计算,一共有多少种不同的剪取方法。
请填写表示方案数目的整数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。
解题思路
我们可以将所有选取5个点的情况全部列出来,然后筛选里面只有一个连通区间的的情况。
问题一
怎么才能列出所有情况呢?
我们可以将12个方格看成一个一维数组,然后对一维数组进行全排列,一维数组中包含5个1(表示选取的5个格子),其余的元素全为0。
至于全排列的思路,可以参考博主另一篇博客:全排列的递归实现
问题二
怎样才能判断选取的5个点是否都是相互连通的呢?
他们相互连通就是代表图中选取的点只能组成一个连通区间,判断连通区间是二维数组(图论)的经典问题,我们可以采用DFS(深度优先搜索)的方法判断整个图中有几个连通区间。
具体思路:循环遍历二维数组,如果遍历到某一个元素为1,就对该元素进行深度搜索,搜索到的地方全部置为0,然后继续循环查找下一个值为1的地方,如果这个图只有一个连通区间,那么在第一次深度搜索时就会将所有元素为1的点置为0;换句话说就是如果只有一个联通区块,那么只需要一次深度搜索就会全部置为0。
代码实现
public class Main {
public static int[] arr = { 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 };//待排(全排列)数组,5个1代表选取的5个点,经过全排列,5个1会散落在不同的位置
public static int count = 0;//符合题意的个数
public static void main(String[] args) {
dfs(0);
System.out.println(count);
}
public static boolean[] visited = new boolean[12];//对应待排集合中的元素,表示对应元素是否被选过
public static int[] path = new int[12];//保存全排列的结果
public static void dfs(int k) {//进行全排列
if (k == 12) {
if (check(path)) {
count++;
}
return;
}
for (int i = 0; i < arr.length; i++) {
if (i > 0 && arr[i] == arr[i - 1] && !visited[i - 1])//这个判断是保证全排列不重复的关键,如果当前元素与前面那个元素相同且前面那个元素并没有被选取的情况下,跳过此时循环
continue;
if (!visited[i]) {
visited[i] = true;
path[k] = arr[i];
dfs(k + 1);
visited[i] = false;//回溯
}
}
}
public static int[][] map = new int[3][4];
private static boolean check(int[] path) {//检查当前全排列的结果是否只有一个连通区间
int num = 0;
for (int i = 0; i < arr.length; i++) {
map[i / 4][i % 4] = path[i];
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
if (map[i][j] == 1) {
dfs(i, j);
num++;
}
}
}
return num == 1;
}
/**
* 将当前连通块全部置为0
* @param i
* @param j
*/
private static void dfs(int i, int j) {
if (map[i][j] == 1) {
map[i][j] = 0;
if (i - 1 >= 0 && map[i - 1][j] == 1)
dfs(i - 1, j);
if (i + 1 < 3 && map[i + 1][j] == 1)
dfs(i + 1, j);
if (j - 1 >= 0 && map[i][j - 1] == 1)
dfs(i, j - 1);
if (j + 1 < 4 && map[i][j + 1] == 1)
dfs(i, j + 1);
}
}
}