题目:1206. 剪格子 - AcWing题库https://www.acwing.com/problem/content/description/1208/
import java.util.Scanner;
public class Main {
public static void main(String args[]){
//读数据
Scanner input = new Scanner(System.in);
String MN = input.nextLine();
String[] mn = MN.split(" ");
int m = Integer.valueOf(mn[0]);
int n = Integer.valueOf(mn[1]);
int[][] map = new int[n][m];
int sum = 0;
int[] ans = {Integer.MAX_VALUE};
for(int i = 0; i < n; i++){
String strNum = input.nextLine();
String[] strTemp = strNum.split(" ");
for(int j = 0; j < m; j++){
map[i][j] = Integer.valueOf(strTemp[j]);
sum += map[i][j];
}
}
//特殊情况:总值不能被平分
if(sum % 2 != 0){
System.out.println(0);
return;
}
boolean[][] isRead = new boolean[n][m];//默认值是false
isRead[0][0] = true;
boolean[] flag = {false};
int[][] direction = {{-1,0},{1,0},{0,-1},{0,1}};//上、下、左、右
dfs(0,0,1,map[0][0],sum,ans,map,direction,isRead,flag);
if(flag[0]){
System.out.println(ans[0]);
}else{
System.out.println(0);
}
}
//line:行 column:列 count:当前格子个数 num:当前数值 sum:总值 ans:最小格子数 map:棋盘 direction:方向 isRead:是否已读 flag:标识
public static void dfs(int line, int column, int count, int num, int sum, int[] ans, int[][] map, int[][] direction, boolean[][] isRead, boolean[] flag){
// //test
//剪枝
if(num > sum / 2 || count > ans[0]){
return;
}
//截止条件
if(num == sum / 2 && isLink(isRead)){
ans[0] = count;
flag[0] = true;
return;
}
//搜索
for(int i = 0; i < 4; i++){//按上下左右的顺序进行遍历
int tline = line + direction[i][0];
int tcolumn = column + direction[i][1];
if(inBounds(tline,tcolumn,map) && !isRead[tline][tcolumn]){//判断是否在界内&是否已经走过
isRead[tline][tcolumn] = true;//标记已走
dfs(tline,tcolumn,count + 1,num + map[tline][tcolumn],sum,ans,map,direction,isRead,flag);
//回溯
isRead[tline][tcolumn] = false;
}
}
}
//判断是否在界内
public static boolean inBounds(int line, int column, int[][] map){
if(line >= 0 && line < map.length && column >= 0 && column < map[0].length){
return true;
}else{
return false;
}
}
//判断是否相连
public static boolean isLink(boolean[][] read){
boolean[][] isRead = read;
int lineNum = isRead.length - 1;
int columnNum = isRead[0].length - 1;
for(int i = 0; i < lineNum + 1; i++){
for(int j = 0; j < columnNum + 1; j++){//遍历
if(i ==0 && j ==0 && isRead[1][0] == isRead[0][1] && isRead[1][0] != isRead[0][0]){//左上角
return false;
}else
if(i == lineNum && j == 0 && isRead[lineNum - 1][0] == isRead[lineNum][1] && isRead[lineNum][1] != isRead[lineNum][0]){//左下角
return false;
}else
if(i == 0 && j == columnNum && isRead[0][columnNum - 1] == isRead[1][columnNum] && isRead[1][columnNum] != isRead[0][columnNum]){//左上角
return false;
}else
if(i == lineNum && j == columnNum && isRead[lineNum][columnNum - 1] == isRead[lineNum - 1][columnNum] && isRead[lineNum - 1][columnNum] != isRead[lineNum][columnNum]){//右下角
return false;
}else
if(i ==0 && j != 0 && j != columnNum){//上边
if(isRead[0][j - 1] == isRead[1][j] == isRead[0][j + 1] && isRead[0][j + 1] != isRead[i][j]){
return false;
}
}else
if(j == 0 && i != 0 && i != lineNum){//左边
if (isRead[i - 1][0] == isRead[i+1][0] == isRead[i][1] && isRead[i][1] != isRead[i][j]){
return false;
}
}else
if(i == lineNum && j !=0 && j != columnNum){//下边
if (isRead[lineNum][j - 1] == isRead[lineNum - 1][j] == isRead[lineNum][j + 1] && isRead[lineNum][j + 1] != isRead[i][j]){
return false;
}
}else
if (j == columnNum && i != 0 && i != lineNum){//右边
if(isRead[i - 1][columnNum] == isRead[i][columnNum - 1] == isRead[i + 1][columnNum]){
return false;
}
}else if(i != 0 && i !=lineNum && j !=0 && j != columnNum){//剩余的
if(isRead[i - 1][j] == isRead[i + 1][j] == isRead[i][j - 1] == isRead[i][j + 1] && isRead[i][j + 1] != isRead[i][j]){
return false;
}
return true;
}
}
}
return false;
}
}
思路:先按规定录入数据,若总值平分后不是整数则不满题意可以直接返回“0”,否则使用深度优先搜索,计算包含左上角的分割区可能包含的最小的格子数目。
一、读取数据&特殊情况
//读数据
Scanner input = new Scanner(System.in);
String MN = input.nextLine();
String[] mn = MN.split(" ");
int m = Integer.valueOf(mn[0]);
int n = Integer.valueOf(mn[1]);
int[][] map = new int[n][m];
int sum = 0;
int[] ans = {Integer.MAX_VALUE};
for(int i = 0; i < n; i++){
String strNum = input.nextLine();
String[] strTemp = strNum.split(" ");
for(int j = 0; j < m; j++){
map[i][j] = Integer.valueOf(strTemp[j]);
sum += map[i][j];
}
}
按照题目要求,读取数据,将int类型的数据存放在二维int数组map中。计算所有格子的值的总和sum。
//特殊情况:总值不能被平分
if(sum % 2 != 0){
System.out.println(0);
return;
}
当总值不能被2整除时,则意味着这个例子不可能被分成累加值相同的两部分,所以可以直接输出0并结束程序。
二、深度优先搜索准备工作
boolean[][] isRead = new boolean[n][m];//默认值是false
isRead[0][0] = true;
boolean[] flag = {false};
int[][] direction = {{-1,0},{1,0},{0,-1},{0,1}};//上、下、左、右
boolean类型的二维数组isRead,用于记录格子是否已经走过,格子的默认值为false经过时改为true,避免一个格子重复走过多遍。因为从坐上角第一个格子出发,因此手动将isRead[0][0]的值设为true。int类型的二维数组direction分别表示上下左右四个方向,这样就可以控制在map中的移动。因为无法获取java方法中的形参,因此耍了一个小伎俩,传了一个boolean数组flag[]进去,flag为是否成功找到的标识,若找到答案则flag为true,反之为false。
三、判断是否在界内的方法
//判断是否在界内
public static boolean inBounds(int line, int column, int[][] map){
if(line >= 0 && line < map.length && column >= 0 && column < map[0].length){
return true;
}else{
return false;
}
}
该方法用于判断是否越界,深度优先搜索中会使用到。map则是存放数据的二维数组,line和column则是当前的坐标,然后让当前坐标与map的对应参数进行对比。如果越界则返回false,否则返回true。
四、判断路径是否连续的方法
//判断是否相连
public static boolean isLink(boolean[][] read){
boolean[][] isRead = read;
int lineNum = isRead.length - 1;
int columnNum = isRead[0].length - 1;
for(int i = 0; i < lineNum + 1; i++){
for(int j = 0; j < columnNum + 1; j++){//遍历
if(i ==0 && j ==0 && isRead[1][0] == isRead[0][1] && isRead[1][0] != isRead[0][0]){//左上角
return false;
}else
if(i == lineNum && j == 0 && isRead[lineNum - 1][0] == isRead[lineNum][1] && isRead[lineNum][1] != isRead[lineNum][0]){//左下角
return false;
}else
if(i == 0 && j == columnNum && isRead[0][columnNum - 1] == isRead[1][columnNum] && isRead[1][columnNum] != isRead[0][columnNum]){//左上角
return false;
}else
if(i == lineNum && j == columnNum && isRead[lineNum][columnNum - 1] == isRead[lineNum - 1][columnNum] && isRead[lineNum - 1][columnNum] != isRead[lineNum][columnNum]){//右下角
return false;
}else
if(i ==0 && j != 0 && j != columnNum){//上边
if(isRead[0][j - 1] == isRead[1][j] == isRead[0][j + 1] && isRead[0][j + 1] != isRead[i][j]){
return false;
}
}else
if(j == 0 && i != 0 && i != lineNum){//左边
if (isRead[i - 1][0] == isRead[i+1][0] == isRead[i][1] && isRead[i][1] != isRead[i][j]){
return false;
}
}else
if(i == lineNum && j !=0 && j != columnNum){//下边
if (isRead[lineNum][j - 1] == isRead[lineNum - 1][j] == isRead[lineNum][j + 1] && isRead[lineNum][j + 1] != isRead[i][j]){
return false;
}
}else
if (j == columnNum && i != 0 && i != lineNum){//右边
if(isRead[i - 1][columnNum] == isRead[i][columnNum - 1] == isRead[i + 1][columnNum]){
return false;
}
}else if(i != 0 && i !=lineNum && j !=0 && j != columnNum){//剩余的
if(isRead[i - 1][j] == isRead[i + 1][j] == isRead[i][j - 1] == isRead[i][j + 1] && isRead[i][j + 1] != isRead[i][j]){
return false;
}
return true;
}
}
}
return false;
}
这个方法也是深度优先搜索中使用到的。题目要求,分为“连续的”两个区域,为了排除那些可以满足“值相同”的要求但是不满足“连续”要求的情况。不同位置的判断条件不同,因此分成三种情况分类讨论:四角、四边、中间部分。为了不改动原来的boolean数组isRead,换了个入参的名,并用临时变量的isRead接受传进来的boolean类型二维数组。遍历这个二维数组,不同的位置按照不同位置的判定规则做出判断。我的思路就是:如果出现类似于围棋“打吃”状况则是不连通的情况。可经过测试,我的思路仅能判断单个格子被分隔开的情况,无法判断下图的情况。(有没有大佬提供一个检验是否联通的方法。)
五、深度优先搜索
//line:行 column:列 count:当前格子个数 num:当前数值 sum:总值 ans:最小格子数 map:棋盘 direction:方向 isRead:是否已读 flag:标识
public static void dfs(int line, int column, int count, int num, int sum, int[] ans, int[][] map, int[][] direction, boolean[][] isRead, boolean[] flag){
// //test
//剪枝
if(num > sum / 2 || count > ans[0]){
return;
}
//截止条件
if(num == sum / 2 && isLink(isRead)){
ans[0] = count;
flag[0] = true;
return;
}
//搜索
for(int i = 0; i < 4; i++){//按上下左右的顺序进行遍历
int tline = line + direction[i][0];
int tcolumn = column + direction[i][1];
if(inBounds(tline,tcolumn,map) && !isRead[tline][tcolumn]){//判断是否在界内&是否已经走过
isRead[tline][tcolumn] = true;//标记已走
dfs(tline,tcolumn,count + 1,num + map[tline][tcolumn],sum,ans,map,direction,isRead,flag);
//回溯
isRead[tline][tcolumn] = false;
}
}
}
每一个格子都可以按照上下左右的移动顺序进行路径的选择。当前累加值刚好为总值的一半时,且两部分联通,即为查找成功。若某一路径查找不成功说明当前选择不合适,则进行回溯,回溯意为“退回”上一步,进行下个选择。移动时需要用到“三”中提到的判断是否在界内的方法,再得到结果时,需要用“四”中提到的判断是否联通的方法。(因为我的该方法仍有bug,所以导致不能Ac)