蓝桥杯 历届试题 剪格子 ——深度优先搜索+全排列

问题描述

如下图所示,3 x 3 的格子中填写了一些整数。

+--*--+--+|10* 1|52|+--****--+|20|30* 1|*******--+| 1| 2| 3|+--+--+--+
我们沿着图中的星号线剪开,得到两个部分,每个部分的数字和都是60。

本题的要求就是请你编程判定:对给定的m x n 的格子中的整数,是否可以分割为两个部分,使得这两个区域的数字和相等。

如果存在多种解答,请输出包含左上角格子的那个区域包含的格子的最小数目。

如果无法分割,则输出 0。

输入格式
程序先读入两个整数 m n 用空格分割 (m,n<10)。

表示表格的宽度和高度。

接下来是n行,每行m个正整数,用空格分开。每个整数不大于10000。

输出格式
输出一个整数,表示在所有解中,包含左上角的分割区可能包含的最小的格子数目。
样例输入1
3 3
10 1 52
20 30 1
1 2 3
样例输出1
3
样例输入2
4 3
1 1 1 1
1 30 80 2
1 1 1 100
样例输出2
10

=============================================================
其实这个题目跟2016的第七届 蓝桥杯 JavaB组 剪邮票的题目十分类似,只不过剪邮票那个是限定剪五个,并且行和列是已经确定下来了的。但方法都是一样,该题是要自己输入行和列的,而且我们要求的就是当剪的两个部分的数字和相等时含左上角区域所需剪的最小格子数。

那么我们可以换一个角度,既然是要求最小的,那我们就从剪一个格子数出发,判断是否有符合即可(当然每次剪格子时都要包含左上角那个)。

代码如下

import java.util.*;

public class Main {
    private static int m;//m列
    private static int n;//n行
    private static int ans;//总和的一半
    private static int count;
    private static int times;
    private static int[][] grid;
    private static boolean result = false;
    //上下左右
    static int[] xx={-1,1,0,0};
    static int[] yy={0,0,-1,1};
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        m = scan.nextInt();//m列
        n = scan.nextInt();//n行
        grid = new int[n][m];
        int sum=0;
        for (int i=0;i<n;i++){
            for (int j=0;j<m;j++){
                grid[i][j]=scan.nextInt();
                sum+=grid[i][j];
            }
        }
        //判断总和是否为偶数
        if (sum%2!=0){
            //如果为奇数,则不可能分成两半
            System.out.println("0");
        }else {
            ans=sum/2;
            //从左上角开始,即从只含第一个点开始
            for(int i=1;i<=(m*n);i++){
                //全排列的点(且必须包含左上角那个点)
                LinkedList<ArrayList<Integer>> output = permuteUnique(i);
                while (output.size()>0){
                    ArrayList<Integer> temp = output.poll();
                    //将起点设置为已访问
                    temp.set(0,0);
                    //次数重置为1
                    times=1;
                    count=grid[0][0];
                    //进行连通性判断
                    dfs(temp,i,0,0);
                    if (result){
                        //表示找到符合要求的直接输出结果
                        System.out.println(i);
                        break;
                    }
                }
                if (result){
                    break;
                }
            }

        }

    }

    /**
     * 先判断是否连通,并且值是否为中和的一半
     * @param temp 连通区域的位置
     * @param total 连通的总数量
     * @param x 目前处于第几行(初始为0)
     * @param y 目前处于第几列(初始为0)
     * @return
     */
    private static void dfs(ArrayList<Integer> temp,int total,int x,int y) {
        if(times==total){
            //如果连通,则判断值是否为对应的情况
            if (count==ans){
                result=true;
            }
            return ;
        }
        for (int i=0;i<4;i++){
            int x1 = x+xx[i];
            int y1 = y+yy[i];
            if (x1>=0&&x1<n&&y1>=0&&y1<m&&temp.get(x1*m+y1)==1){
                temp.set(x1*m+y1,0);
                count+=grid[x1][y1];
                times++;
                dfs(temp,total,x1,y1);
            }
        }


    }

    /**
     * 全排列
     * @param i 全排列中i的个数
     * @return
     */
    public static LinkedList<ArrayList<Integer>> permuteUnique(int i) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int j=0;j<n*m;j++) {
            if (j<i){
                list.add(1);
            }else {
                list.add(0);
            }
        }
        LinkedList<ArrayList<Integer>> output = new LinkedList<ArrayList<Integer>>();
        backstack(n*m,list,output,0);
        return output;
    }

    public static void backstack(int num, ArrayList<Integer> list, LinkedList<ArrayList<Integer>> output, int first){
        //只需在这里添加判断是否添加进去的序列已存在即可
        if (first==num){
            if (list.get(0)==1){
                //只有该全排列中的第一个值为1才加入
                output.add(new ArrayList<Integer>(list));
            }
            return ;
        }
        for (int i=first;i<num;i++){
            if(canSwap(list,first,i)){
                //先判断交换的两个数是否相同
                Collections.swap(list,i,first);
                //交换后进行回溯
                backstack(num,list,output,first+1);
                //再换回来
                Collections.swap(list,first,i);
            }
        }

    }
    private static boolean canSwap(List<Integer> list,int begin,int end){
        for(int i=begin;i<end;i++){
            if(list.get(i)==list.get(end)){
                return false;
            }
        }
        return true;
    }

}

在这里插入图片描述
给几个测试数据可以自己写出来测试下是不是正确的
input:
4 5
10 20 30 1
1 1 40 1
1 60 50 1
1 150 1 1
1 1 1 348
output:
7

========
input:
4 4
100 2 2 2
1 30 30 1
30 2 20 1
2 1 1 1
output:
10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值