《从入门到精通:蓝桥杯编程大赛知识点全攻略》(四)-飞行员兄弟、二分、数的范围

本片博客主要介绍了用递推来解决问题,并通过讲解二分的各种情况和步骤,让大家熟悉如何使用二分,并以数的范围这道题来解释。


前言

本片博客主要介绍了用递推来解决问题,并通过讲解二分的各种情况和步骤,让大家熟悉如何使用二分,并以数的范围这道题来解释。


飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 16 个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个 4×4 的矩阵,您可以改变任何一个位置 [i,j] 上把手的状态。

但是,这也会使得第 i行和第 j 列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式

输入一共包含四行,每行包含四个把手的初始状态。

符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。

至少一个手柄的初始状态是关闭的。

输出格式

第一行输出一个整数 N,表示所需的最小切换把手次数。

接下来 N 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围

1≤i,j≤4

输入样例:

-+--
----
----
-+--

输出样例:

6
1 1
1 3
1 4
4 1
4 3
4 4

算法思路

 

我们可以直接暴力枚举所有的方案,4行4列,那么共有16个位置,每个位置都有操作和不操作两种方案,那么最后共有2^16 = 65536种方案,枚举每一种方案,当最后的冰箱全是打开状态,然后将每一种方案操作的行列数记录,最后将每一种方案的次数进行比较,最后得到次数最小的方案即是最后的结果。

用二维字符数组grap来存储初始数据,因为要枚举65536种方案,我们再使用一个二维字符数组backup来备份数据。然后用op来表示操作,枚举65536次,其中我们把二维的坐标用一维来表示,然后再用二进制数字某个特定为是否为1。(具体只是为了枚举65536种方案,我们该对哪一个位置进行修改或不修改操作)将操作对应的行和列号用来Pair来存储,放到一个列表list中。

跟(《从入门到精通:蓝桥杯编程大赛知识点全攻略》(三)-费解的开关、翻硬币-优快云博客)中费解的开关的op性质一样,可以参考一下这篇博客

将二维转换成一维数字表示get(int x,int y)函数,具体表示为4 * x + y

 用一个布尔类型变量close来表示所有的冰箱是否处于打开状态,true为存在冰箱有关闭状态,false为全打开状态。因为题目中没有说所有数据都存在答案,我们检测一下,存在关闭就说明该方案行不通。当处于全打开状态,我们就需要最后储存答案的列表res和每一种方案存储改变行号和列号的步骤的列表list作比较,最后要得到改变行号和列好次数最少的答案。

其中turn_all(int x,int y)函数是来改变坐标(x,y)和所在行和列,做一个循环分别改变行和列,因为行和列都改变坐标(x,y),所以相当于没改变,最后还要再改变一次(x,y)本身。

代码如下


import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;


public class Main {
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(br);
    static int N = 5;
    static char[][] grap = new char[N][N];
    static char[][] backup = new char[N][N];
    public static void main(String[] args) throws IOException {
        for(int i = 0;i < 4;i++){
            grap[i] = nextLine().toCharArray();
        }
        ArrayList<Pair> res = new ArrayList<Pair>();
        for(int op = 0;op < 65536;op++){
            //备份
            for(int i  = 0;i < 4;i++){
                backup[i] = Arrays.copyOf(grap[i], grap[i].length);
            }
            ArrayList<Pair> list = new ArrayList<Pair>();
            //进行操作
            for(int i = 0;i < 4;i++){
                for(int j = 0;j < 4;j++){
                    if((op >> get(i,j) & 1) == 1){
                        list.add(new Pair(i,j));
                        turn_all(i,j);
                    }
                }
            }
            boolean close = false;
            //判断冰箱是否为全打开
            for (int i = 0;i < 4;i++){
                for(int j = 0;j < 4;j++){
                    if(grap[i][j] == '+'){
                        close = true;
                    }
                }
            }
            if(!close){
                if(res.isEmpty() || res.size() > list.size()){
                    res = list;
                }
            }

            //还原
            for(int i = 0;i < 4;i++){
                grap[i] = Arrays.copyOf(backup[i], backup[i].length);
            }


        }
        pw.println(res.size());
        for(int i = 0;i < res.size();i++){
            pw.println((res.get(i).x+1)+" "+(res.get(i).y+1));
        }
        pw.flush();
    }
    //将二维数组转换成一维
    public static int get(int x,int y){
        return 4 * x + y;
    }
    public static void turn_all(int x,int y){
        for(int i = 0;i < 4;i++){
            turn_one(x,i);
            turn_one(i,y);
        }
        turn_one(x,y);
    }
    public static void turn_one(int x,int y){
        if(grap[x][y] == '+'){
            grap[x][y] = '-';
        }else {
            grap[x][y] = '+';
        }
    }

    public static String nextLine() throws IOException {
        return br.readLine();
    }
}
class Pair{
    int x;
    int y;
    public Pair(int x,int y){
        this.x = x;
        this.y = y;
    }
}

二分

上述两种方法写二分就可以避免死循环的出现。

第一种情况:ans在M的右边,当L = R - 1,即说明区间只有两个元素,此时M = ( L + R ) / 2为L,但正确答案确是R,满足if条件时 L = M = L,会一直卡在这,成为死循环,所以 M = (L + R + 1) / 2就就可以避免这种情况。

整数二分步骤:

1.找一个区间[L,R],使得答案一定在该区间中

2.找一个判断条件,使得该判断条件具有二段性,并且答案一定是该二段性的分界点

3.分析中点M在该判断条件下是否成立,如果成立,考虑在哪个区间;如果不成立,考虑答案在哪个区间。

4.如果更新方式写的是R=M,则不需要做任何操作;更新方式写的是L = M,则需要在计算M的时候加上1。

数的范围

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1

输入格式

第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤100000
1≤q≤10000
1≤k≤10000

输入样例

6 3
1 2 2 3 3 4
3
4
5

输出样例

3 4
5 5
-1 -1

算法思路

主要分两部分,分别找区间的左端点和右端点。

用整型数组arr来存储数组,左端点left = 0,右端点right = n - 1,mid = (left + right)/ 2,当

arr[mid] >= x说明mid及其左边可能含有值为x的元素,把右边界左移;当 arr[mid] < x说明此时左边及arr[mid]不可能存在x,所以左边界右移,left = mid + 1;最后当left = right,此时我们得到的arr[left]所在的若是x一定是x元素出现的最小位置,我们就得到了区间的左端点。

当arr[mid] <=  x,说明mid及其右边可能含有值为x的元素,所以左端点右移;当arr[mid] > x,说明待查元素只会在mid的左边,right = mid - 1;最后当right = left时,arr[right]所在的若是x一定是x元素出现的最大位置我们就得到了右端点。

代码如下

package AcWingLanQiao;
import java.io.*;
public class 数的范围 {
    static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static StreamTokenizer st = new StreamTokenizer(br);
    static int[] arr =  new int[100010];
    public static void main(String[] args)throws Exception {
        int n = nextInt();
        int m = nextInt();
        for(int i=0;i<n;i++){
            arr[i] = nextInt();
        }
        while(m-->0){
            int x = nextInt();
            //二分x的左端点
            int left = 0;
            int right = n - 1;
            while(left<right){
                int mid = (left+right)/2;
                if(arr[mid] >= x){
                    right = mid;
                }else {
                    left = mid + 1;
                }
            }
            if(arr[left] == x){
                pw.print(left+" ");
                right = n - 1;
                while(left<right){
                    int mid = (left+right + 1)/2;
                    if(arr[mid] <= x){
                        left = mid ;
                    }else {
                        right = mid - 1;
                    }
                }
                pw.println(right);
            }
            else{
                pw.println("-1 -1");
            }
        }

        pw.flush();
    }
    public static int nextInt()throws Exception{
        st.nextToken();
        return (int)st.nval;
    }
}

总结

希望这篇博客让大家更好的了解递推和二分,解决遇到的一类问题!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值