AcWing 95. 费解的开关(指数型枚举)

95. 费解的开关

题意:
  • 给定一个5x5的方格,共25盏灯
  • 每盏灯有开和关两种状态
  • 每次操作一盏灯时,以该灯为中心的十字形状范围的灯都会改变状态
  • 找到用最少的操作步数使所有的灯都亮着,当步数超过6时返回-1
思路1:
  • 暴力枚举出所有整个矩阵所有的操作情况
  • 时间复杂度: 2 25 ∗ 25 ∗ 5 ∗ 500 2^{25}*25*5*500 225255500 远大于 1e,不可行
思路2:
  • 枚举出对第一行的所有操作情况(指数类型枚举模型),注意这里不是枚举第一行的灯的状态
  • 根据推导可知:当第一行操作完毕确认完的状态后,后面除了最后一行的每一行操作就已经确定了,即必须在上一行关灯的下方一个位置进行状态变化
  • 当操作到最后一行到时候,前面的n-1行已经全亮
  • 最后只需要特判一下最后一个行的状态,只要有一个不亮,则说明第一行的本次操作情况无效
  • 时间复杂度:32255*500 远小于 1e,所以可行
import java.util.*;
import java.io.*;
class Main {
    Scanner s = new Scanner(System.in);
    
    // 矩阵数量
    int n;
    
    // 输入的每一行
    String line;
    
    // char数组
    char[][] g;
    
    public void run() {
        n = s.nextInt();
        g = new char[5][5];
        
        while(n-- > 0) {
            // 填充数组
            for (int i = 0; i < 5; i++) {
                line = s.next();
                for (int j = 0; j < 5; j++) {
                    g[i][j] = line.charAt(j);
                }
            }
            
            // 输出每次输入的g的ans
            minStep(g);
        }
    }
    
    public void minStep(char[][] c) {
        
        // 输入数组的替身
        char[][] backup = new char[5][5];
        
        for (int k = 0; k < 5; k++) {
            System.arraycopy(c[k], 0, backup[k], 0, 5);
        }
        
        
        int ans = 7;
        
        // 先枚举出第一行的所有操作情况
        for (int op = 0; op < 32; op++) {
            
            // 每次op让step归0
            int step = 0;
            
            // 二进制数中对数字为1的位置进行翻转 
            for (int i = 0; i < 5; i++) {
                if ((op >> i & 1) == 1) {
                    turn(backup, 0, i);
                    step++;
                }
            }
            
            // 从第一行到第四行开始扫描
            for (int i = 0; i < 4; i++) {
                
                for (int j = 0; j < 5; j++) {
                    // 如果当前行的当前列元素为0则对下一行对应位置进行翻转
                    if (backup[i][j] == '0') {
                        turn(backup, i + 1, j); 
                        step++;
                    }
                }
            }
            
            boolean bright = true;
            
            // 扫描最后一行元素,判断其是否全为1
            for (int i = 0; i < 5; i++) {
                if(backup[4][i] != '1') {
                    bright = false;
                }
            }
            
            // 如果最后一行全亮,则本次op有效,与前一次的移动步数比较,以至于所有op结束后找到最小值
            if (bright) {
                ans = Math.min(ans, step);
            }
            
            // 恢复替身改变之前的状态
            for (int k = 0; k < 5; k++) {
                System.arraycopy(c[k], 0, backup[k], 0, 5);
            }
        }
        
        // 判断最小步数是否大于6,如果大于6则返回-1
        if(ans > 6) ans = -1;
        System.out.println(ans);
    }
    
    // 关于(x, y)的5个点偏移量
    int[] dx = {-1, 0, 1, 0, 0};
    int[] dy = {0, 1, 0, -1, 0};
    
    public void turn(char[][] c, int x, int y) {
        
        for (int i = 0; i < 5; i++) {
            // (a, b)为(x, y)附加对应偏移量的点
            int a = x + dx[i];
            int b = y + dy[i];
            
            // 判断一下边界,如果越界,这该点无效,不需要翻转
            if (a < 0 || a >=5 || b < 0 || b >= 5) continue;
            
            // 对该点进行翻转
            c[a][b] = c[a][b] == '1' ? '0' : '1';
        }
    }
    
    public static  void main(String[] args) {new Main().run();}
}

收获

©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页