首页 > 其他分享 >剪邮票

剪邮票

时间:2023-02-04 21:23:08浏览次数:41  
标签:邮票 return int dfs ++ step static

剪邮票

题目:

如【图1.jpg】, 有12张连在一起的12生肖的邮票。

现在你要从中剪下 55 张来,要求必须是连着的。

(仅仅连接一个角不算相连) 比如,【图2.jpg】,【图3.jpg】中,粉红色所示部分就是合格的剪取。

请你计算,一共有多少种不同的剪取方法。

知识点:

  1. 全排列+去重
  2. DFS
  3. 判断有几个连通块

知识点一:全排列

所谓全排列,就是打印出字符串中所有字符的所有排列。例如输入字符串abc,则打印出 a、b、c 所能排列出来的所有字符串 abcacbbacbcacabcba

方法一:暴力\((O(n^n))\)

给出的数比较少可以用暴力循环求解,以1,2,3为例

public static void main(String[] args) {
		for(int i=1;i<=3;i++)
		{
			for(int j=1;j<=3;j++) {
				for(int k=1;k<=3;k++) {
					if(i!=j&&j!=k&&k!=i) {
						System.out.println(i+" "+j+" "+k);
					}
				}
			}
		}
	}

方法二: DFS

public class Main1 {

	static int[] a = { 1, 1, 3, 4 };
	static int[] path = new int[4];// 存放结果的数组
	static boolean[] flag = new boolean[4];// 判断该数是否已经被用过
	static int res = 0;

	public static void main(String[] args) {
		dfs(0);
		System.out.println(res);
	}

	static void dfs(int step) {
		if (step == 4) {
			for (int i = 0; i < 4; i++) {
				System.out.print(path[i] + " ");
			}
			res++;
			System.out.println();
			return;
		}

		for (int i = 0; i < 4; i++) {

			if (flag[i] == false) {//没有被用过的元素可以抓入到path中
				flag[i] = true;//标记为已访问过
				path[step] = a[i];//将a[i]放入path[step]中
				dfs(step + 1);//递归
				flag[i] = false;//回溯
			}
		}
		return;
	}
    
}

去重:

如果a中元素有重复的话,全排列也会有重复的

如何避免重复?

相同元素抓取时,无论先抓哪个造成的结果都是一样的,规定在抓取相同元素时,排列在前的元素先被抓,要避免抓取一个元素时,前面有与之相同的元素未被抓取。

在dfs中加入判断

if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)//现在准备选取的元素和上一个元素相同,但是上一个元素还没被使用
				continue;
	static void dfs(int step) {
		if (step == 4) {
			for (int i = 0; i < 4; i++) {
				System.out.print(path[i] + " ");
			}
			res++;
			System.out.println();
			return;
		}

		for (int i = 0; i < 4; i++) {
			if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)
				continue;
			if (flag[i] == false) {
				flag[i] = true;
				path[step] = a[i];
				dfs(step + 1);
				flag[i] = false;
			}
		}
		return;
	}

方法三:交换法

给定任意个数字,求其全排列

助于理解的点

  • 全排列就是从第一个数字起每个数分别与它后面的数字交换
public class Main1 {

	static int[] a = { 1, 1, 3, 4 };
	static int len;

	public static void main(String[] args) {
		len = a.length;
		dfs(0);
	}

	static void dfs(int step) {
		if (step == len) {
			for (int i = 0; i < len; i++)
				System.out.print(a[i] + " ");
			System.out.println();
			return;
		}

		for (int i = step; i < len; i++) {
			int t = a[i];
			a[i] = a[step];
			a[step] = t;
			dfs(step + 1);
			t = a[i];
			a[i] = a[step];
			a[step] = t;
		}
		return;
	}
}

去重

去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。用编程的话描述就是第i个数与第j个数交换时,要求[i,j)中没有与第j个数相等的数。

public class Main1 {

	static int[] a = { 1, 1, 3, 4 };
	static int len;

	public static void main(String[] args) {
		len = a.length;
		dfs(0);
	}

	// 判断是否要交换
	static boolean isSwap(int start, int end) {
		for (; start < end; start++) {
			if (a[start] == a[end])
				return false;
		}
		return true;
	}

	static void dfs(int step) {
		if (step == len) {
			for (int i = 0; i < len; i++)
				System.out.print(a[i] + " ");
			System.out.println();
			return;
		}

		for (int i = step; i < len; i++) {
			if (isSwap(step, i)) {
				int t = a[i];
				a[i] = a[step];
				a[step] = t;
				dfs(step + 1);
				t = a[i];
				a[i] = a[step];
				a[step] = t;
			}
		}
		return;
	}
}

思路:

可以将问题分为两部分:

  1. 12个邮票任选5个

    • 全排列去重问题,在12个里选5个不重复的
  2. 这5个是否满足题意,满足即为一种解法,让计数变量ans++;

    • 将一维数组a转化为二维数组g,再判断这五个方块是否为连通块

      static int[][] next = { { 1, 0 }, // 向下
      			{ -1, 0 }, // 向上
      			{ 0, 1 }, // 向右
      			{ 0, -1 }// 向左
      	};
      // 判断有几个连通块
      	static void dfs(int i, int j) {
      		g[i][j] = 0;
      		for (int i1 = 0; i1 < 4; i1++) {
      			int x = i + next[i1][0];
      			int y = j + next[i1][1];
      			if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1)
      				dfs(x, y);
      		}
      	}
      

Code(DFS求全排列)

public class Main {
	static long res = 0;
	static int path[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	static boolean[] flag = new boolean[12];
	static int a[] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 };// 它的每个排列代表12选5的1个方案
	static int[][] g = new int[3][4];
	static int[][] next = { { 1, 0 }, // 向下
			{ -1, 0 }, // 向上
			{ 0, 1 }, // 向右
			{ 0, -1 }// 向左
	};

	// 判断有几个连通块中的dfs
	static void dfs(int i, int j) {
		g[i][j] = 0;
		for (int i1 = 0; i1 < 4; i1++) {
			int x = i + next[i1][0];
			int y = j + next[i1][1];
			if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1)
				dfs(x, y);
		}
	}

	// 判断有几个连通块
	static boolean check() {
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				if (path[i * 4 + j] == 1)
					g[i][j] = 1;
				else
					g[i][j] = 0;
			}
		}
		int cnt = 0;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				if (g[i][j] == 1) {
					dfs(i, j);
					cnt++;
				}
			}
		}
		return cnt == 1;
	}

	// 全排列
	static void df(int k) {
		if (k == 12) {
			if (check())
				res++;
			return;
		}

		for (int i = 0; i < 12; i++) {
			if (i > 0 && a[i] == a[i - 1] && flag[i - 1] == false)
				continue;
			if (flag[i] == false) {
				flag[i] = true;
				path[k] = a[i];
				df(k + 1);
				flag[i] = false;
			}
		}

		return;
	}

	public static void main(String[] args) {
		df(0);
		System.out.println(res);
	}

}

Code(交换法求全排列)

public class Main{

	static long res = 0;
	static int a[] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 };// 它的每个排列代表12选5的1个方案
	static int[][] g = new int[3][4];
	static int[][] next = { { 1, 0 }, // 向下
			{ -1, 0 }, // 向上
			{ 0, 1 }, // 向右
			{ 0, -1 }// 向左
	};

	// 判断有几个连通块中的dfs
	static void dfs(int i, int j) {
		g[i][j] = 0;
		for (int i1 = 0; i1 < 4; i1++) {
			int x = i + next[i1][0];
			int y = j + next[i1][1];
			if (x >= 0 && x < 3 && y >= 0 && y < 4 && g[x][y] == 1)
				dfs(x, y);
		}
	}

	// 判断有几个连通块
	static boolean check() {
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				// 将1维数组转化为2维数组
				if (a[i * 4 + j] == 1)
					g[i][j] = 1;
				else
					g[i][j] = 0;
			}
		}
		int cnt = 0;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 4; j++) {
				if (g[i][j] == 1) {
					// dfs几次则代表有几个连通块
					dfs(i, j);
					cnt++;
				}
			}
		}
		return cnt == 1;
	}

	// 判断是否要交换
	static boolean isSwap(int start, int end) {
		for (; start < end; start++) {
			if (a[start] == a[end])
				return false;
		}
		return true;
	}

	// 全排列
	static void df(int k) {
		if (k == 12) {
			if (check())
				res++;
			return;
		}

		for (int i = k; i < 12; i++) {
			if (isSwap(k, i)) {
				int t = a[i];
				a[i] = a[k];
				a[k] = t;
				df(k + 1);
				t = a[i];
				a[i] = a[k];
				a[k] = t;
			}
		}

		return;

	}

	public static void main(String[] args) {
		df(0);
		System.out.println(res);
	}

}

参考视频:

2016第七届蓝桥杯省赛真题解析T7剪邮票_哔哩哔哩_bilibili

标签:邮票,return,int,dfs,++,step,static
From: https://www.cnblogs.com/ANDQE/p/17092403.html

相关文章

  • 收集邮票-数学期望
    收集邮票题目描述有\(n\)种不同的邮票,皮皮想收集所有种类的邮票。唯一的收集方法是到同学凡凡那里购买,每次只能买一张,并且买到的邮票究竟是\(n\)种邮票中的哪一种是......
  • 【题解】洛谷P2725 [USACO3.1]邮票 Stamps
    从n种邮票中选出不超过k张邮票,使选出来的邮票可以表示1~m之间(含)的所有数。每张邮票在不超过k的前提下,都可以使用无数次,因此可以将问题看成一个完全背包问题。n种邮票就是n......