Juego de rompecabezas | Siente el encanto de los algoritmos

3. Juegos de rompecabezas

Los grados 10 hora de apertura Lunes, 7 de septiembre de 2020 09:00
descuento 0,8 Tiempo de descuento Martes, 15 de septiembre de 2020 09:00
Se permite la presentación tardía No Hora de cierre Sábado, 10 de octubre de 2020 23:00

Descripción

Xiao Zhang es un fanático de la sala de escape. En un juego de sala de escape, debes resolver una serie de acertijos y finalmente obtener la contraseña para salir. Ahora Xiao Zhang necesita abrir una caja que contiene pistas, pero hay un candado de combinación como se muestra en la imagen de abajo.

Cada punto es un botón y hay una pequeña luz dentro de cada botón. Como se muestra en la figura anterior, rojo significa que la luz está encendida y blanco significa que la luz está apagada. Siempre que se presiona el botón, la lámpara de este botón , así como los botones de cuatro direcciones de la lámpara vertical y horizontal, cambian de estado (si las luces están apagadas, si las luces están apagadas). Si Xiao Zhang apaga las luces presionando el botón, puede abrir la caja.

Para este bloqueo de código, primero podemos presionar el botón en la esquina superior izquierda, y el estado de bloqueo de código cambia a la siguiente figura.

Luego presione el botón en la esquina inferior derecha, y el estado de bloqueo del código cambiará a la siguiente figura.

Finalmente presione el botón central, las luces están apagadas.

Ahora Xiao Zhang le da un estado de bloqueo de código, por favor dígale que presione el botón al menos un par de veces para apagar todas las luces.

Entrada

Dos enteros en la primera línea norte, m (1 \ leq n, m \ leq 16).

En la siguiente nortelínea, cada línea tiene una metrocadena de caracteres de longitud 01, 0 significa que el estado inicial de la luz está apagado y 1 significa que el estado inicial de la luz está encendido.

Salida

Un número entero en una línea significa que todas las luces se pueden apagar presionando el botón al menos varias veces.

Notas

Para el primer ejemplo, vea la descripción del título, y para el segundo ejemplo, presione los botones superior izquierdo e inferior derecho.

Se garantiza la resolución del caso de prueba .

  Entrada de prueba Rendimiento esperado límite de tiempo Limite de memoria Proceso extra
Caso de prueba 1 Mostrar como texto
  1. 3 3↵
  2. 100↵
  3. 010↵
  4. 001↵
Mostrar como texto
  1. 3↵
1 segundo Los 64M 0
Caso de prueba 2 Mostrar como texto
  1. 2 3↵
  2. 111↵
  3. 111↵
Mostrar como texto
  1. 2↵
1 segundo Los 64M 0


       Para esta pregunta emmm, pensé en dos métodos: el método de eliminación gaussiana o la búsqueda en profundidad. Si eres el primer contacto, debería ser difícil de entender, pero no importa si no lo entiendes por completo, solo léelo unas cuantas veces y elimínalo ...

        Hablemos del método de búsqueda en profundidad (dfs).

        Puede comprender la búsqueda profunda como esta: enumere todos los casos uno por uno , hasta que se complete la enumeración, buscamos un resultado.


Ideas

Para esta pregunta, para encontrar el número mínimo de pulsaciones de teclas, primero debe saber:

  • Presione un botón como máximo una vez: si hay dos o más veces, habrá compensación, entonces sus pasos definitivamente no son los menos importantes.
  • El orden de las teclas no tiene nada que ver con el resultado final: se presionan las mismas teclas en un orden diferente, por supuesto, el resultado es el mismo.

       Si enumeramos violentamente : hay dos casos para cada tecla (presionar o no mover), entonces necesitamos enumerar la mayoría de las  2 ^ {16 \ ast 16}veces, que es realmente un número terrible ... entonces debe haber un algoritmo mejor. La razón del pensamiento a continuación emmm solo puede ser indescriptible, sin mencionar la idea, es suficiente que puedas entender este método después de leerlo. Vaya directamente al algoritmo central :

  1.  En primer lugar, solo enumeramos las pulsaciones de teclas de la primera fila de luces : luego tenemos que enumerar la mayoría de las  2 ^ {16}veces, lo que sigue siendo aceptable ... Registre el número de veces que se presiona la primera fila para cada recuento de enumeración
  2. Para los resultados de cada enumeración (se ha operado la primera fila de luces), los procesaremos fila por fila . (Por ejemplo, si ciertas luces en la primera fila están encendidas, presionamos las luces en la siguiente fila, de modo que todas las luces en la primera fila se apaguen presionando la segunda fila de botones; luego discutiremos la segunda fila y usaremos la tercera El botón de la fila apaga la segunda fila ... y así sucesivamente). Si finalmente se arregla la penúltima fila, la última fila también desaparece por completo, lo que indica que esta enumeración es factible y se registra el número de pulsaciones de tecla durante todo el proceso; de lo contrario, esta enumeración no es factible y debemos buscar otros elementos. Cita el programa.
  3. Para una determinada enumeración, entonces la operación línea por línea se extingue con éxito, entonces significa que usamos count + cur times para completar, esta vez necesitamos actualizar el valor mínimo de la respuesta.

Código

        La implementación del código de esta pregunta es más complicada, lo que ejercita la capacidad de programación. Si no puede compensarlo, no se desanime demasiado, solo podrá familiarizarse con él lentamente. También es difícil brindarle la capacidad de programar en la solución de un problema. Esta capacidad se basa en un poco de código suyo, ¿verdad? Pero para brindarle un código mejor legible para su referencia. La parte muy importante de aprender a programar es aprender. Código de otras personas ~

        Tal vez todavía escribas todo el código en la función principal deficiente ( si no, no lo dije ), el primer paso para mejorar la legibilidad del programa: escribir funciones por función.

        En primer lugar, escribiré algunos módulos de funciones para usar ( debería poder comprender con los comentarios, si no, aprenda primero la sintaxis de c / c ++ ) y las funciones que se llamarán más adelante. Las variables globales tienen estos

#define MAX_LEN 17

int n, m, a[MAX_LEN][MAX_LEN];  //题中的输入
int cur[MAX_LEN][MAX_LEN];  //存储处理完第一排后的状态
int ans = 256;   //存储答案:最小步骤。初始为最大值256步

1. Una función que implementa la operación XOR en un entero

/* 将p位置上的整数做一个反(异或)操作:
 * 1变成0, 0变成1 */
void change(int *p) {
    if (*p == 1)
        *p = 0;
    else
        *p = 1;
}

2. La función que realiza el cambio de la prensa ligera

/* 设定将a[i][j]处按下所产生的反应
 * 注意:a数组根据传递的地址而定 */
void push(int a[][MAX_LEN], int i, int j) {
    change(&a[i][j]);
    if (i - 1 >= 0)
        change(&a[i - 1][j]);
    if (j - 1 >= 0)
        change(&a[i][j - 1]);
    if (i + 1 < n)
        change(&a[i + 1][j]);
    if (j + 1 < m)
        change(&a[i][j + 1]);
}

3. Primera búsqueda de profundidad (enumere el estado del botón de la primera fila de luces)

         Independientemente de la función calc llamada en la línea 8 aquí, solo tiene que esperar hasta que la función calc pueda calcular el número de pasos subsiguientes para una determinada enumeración. Para comprender capa por capa, primero comprenda el funcionamiento de dfs, que es el más difícil de comprender. Para ser una digresión, estaba muy mareado cuando escribí el programa dfs por primera vez ... dfs es esencialmente recursivo, el secreto de mirar el programa dfs es: mírelo macroscópicamente, cuando se llama a sí mismo recursivamente, tiene una comprensión macro de la llamada recursiva aquí Como resultado, no es necesario profundizar demasiado en la función recursiva. ( Emmmm, si no entiendes esta oración, la entenderás tarde o temprano ... Después de muchas palizas recursivas complejas, reconocerás mucho esta oración )

     Por ejemplo, déjame llevarte a comprender los siguientes dfs:

     Enumeramos la selección del botón del paso 1 en la primera fila: presione primero, luego considere la enumeración del siguiente botón; restaure (es decir, no presione) y luego considere la enumeración del siguiente botón. Si puede comprender esta idea a un nivel macro y luego estudia detenidamente la implementación recursiva de este código, la comprenderá mucho más rápido. ( Si eres un hermano mayor, trátalo como si no lo dijera, si no lo entiendes, lo leerás unas cuantas veces más ).

/* 深度优先搜索,枚举第一排按键的所有按法
 * step: 当前讨论第1排第step + 1个按键
 * count: step之前按键一共被操作了的次数和 */
void dfs(int step, int count) {
    // 深度优先搜索的终点:讨论完第一排最后一个按键了
    // 已经按照一种方式将第一排操作完成
    if (step == m) {
        int t = calc();  //计算该基础上熄灭所有的灯需要的步数
        if (t == -1)  //无解
            return;
        if (t + count < ans)  //有解,更新最小值
            ans = t + count;
        return;
    }

    push(a, 0, step);  //按下第一排第step-1个按键
    dfs(step + 1, count + 1);

    push(a, 0, step); //再次按下(相当于还原)第一排第step-1个按键
    dfs(step + 1, count);
}

4. Función Calc

        La función getCur () se llama en calc, principalmente para copiar el estado del resultado de la enumeración a la matriz cur; de lo contrario, es difícil restaurar la operación directa en la matriz a, lo que afectará la discusión más adelante. La idea de la función calc es el segundo paso resumido en nuestro algoritmo, que va línea por línea.

/* 当第一排的灯被弄完后,把灯的状态复制到cur数组中
 * 便于后面的计数与操作,不会影响到原数组a */
void getCur() {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cur[i][j] = a[i][j];
}

/* 当第一排的灯被弄完后
 * 逐排操作,计算是否能够全部灭完
 * 如果不可以:返回-1;否则:返回操作次数 */
int calc() {
    getCur();
    int step = 0;
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < m; j++) {
            if (cur[i][j] == 1) {
                step++;
                push(cur, i + 1, j);
            }
        }

    for (int i = 0; i < m; i++)
        if (cur[n - 1][i] == 1)
            return -1;
    return step;
}


Código completo

       Muy bien ... Las alitas de pollo están demasiado cuidando a los niños, y tengo que desmontar las funciones una por una ... Publiquemos el código completo a continuación, los grandes pueden venir a ver el código completo. Principalmente escribí dfs al comienzo del período de la escuela primaria. Me temo que los lectores no lo aceptarán, por lo que pueden pensar que soy muy prolijo. Tal vez resuelva este problema a las 3 en punto de la noche. Es realmente largo ...

       El siguiente es el código completo, la función principal es principalmente procesar la entrada. Preste especial atención: ¡ succionar los caracteres de nueva línea en cada línea! De lo contrario ... no sabes por qué las alitas de pollo son un problema hasta altas horas de la noche, porque ha pasado un día entero porque no chupaba el carácter de nueva línea cada vez ... He estado revisando varios lugares de búsqueda recursiva y profunda. Como resultado, mis lágrimas se derramaron cuando se depuró el error a las 2 en punto ...

#include <cstdio>

#define MAX_LEN 17

int n, m, a[MAX_LEN][MAX_LEN];
int cur[MAX_LEN][MAX_LEN];  //存储处理完第一排后的状态
int ans = 256;   //存储答案:最小步骤。初始为最大值256步

/* 将p位置上的整数做一个反(异或)操作:
 * 1变成0, 0变成1 */
void change(int *p) {
    if (*p == 1)
        *p = 0;
    else
        *p = 1;
}

/* 设定将a[i][j]处按下所产生的反应
 * 注意:a数组根据传递的地址而定 */
void push(int a[][MAX_LEN], int i, int j) {
    change(&a[i][j]);
    if (i - 1 >= 0)
        change(&a[i - 1][j]);
    if (j - 1 >= 0)
        change(&a[i][j - 1]);
    if (i + 1 < n)
        change(&a[i + 1][j]);
    if (j + 1 < m)
        change(&a[i][j + 1]);
}

/* 当第一排的灯被弄完后,把灯的状态复制到cur数组中
 * 便于后面的计数与操作,不会影响到原数组a */
void getCur() {
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cur[i][j] = a[i][j];
}

/* 当第一排的灯被弄完后
 * 逐排操作,计算是否能够全部灭完
 * 如果不可以:返回-1;否则:返回操作次数 */
int calc() {
    getCur();
    int step = 0;
    for (int i = 0; i < n - 1; i++)
        for (int j = 0; j < m; j++) {
            if (cur[i][j] == 1) {
                step++;
                push(cur, i + 1, j);
            }
        }

    for (int i = 0; i < m; i++)
        if (cur[n - 1][i] == 1)
            return -1;
    return step;
}

/* 深度优先搜索,枚举第一排按键的所有按法
 * step: 当前讨论第1排第step + 1个按键
 * count: step之前按键一共被操作了的次数和 */
void dfs(int step, int count) {
    // 深度优先搜索的终点:讨论完第一排最后一个按键了
    // 已经按照一种方式将第一排操作完成
    if (step == m) {
        int t = calc();  //计算该基础上熄灭所有的灯需要的步数
        if (t == -1)  //无解
            return;
        if (t + count < ans)  //有解,更新最小值
            ans = t + count;
        return;
    }

    push(a, 0, step);  //按下第一排第step-1个按键
    dfs(step + 1, count + 1);

    push(a, 0, step); //再次按下(相当于还原)第一排第step-1个按键
    dfs(step + 1, count);
}

int main() {
    scanf("%d%d\n", &n, &m);  //一定要加上\n,作用:吸去换行符

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            char c;
            c = getchar();
            a[i][j] = c - '0';  //将字符转化为整数考虑
        }
        getchar();  //吸去换行符
    }

    dfs(0, 0);
    printf("%d\n", ans);
}


Bienvenido a prestar atención a la cuenta pública personal "  Programación de ala de pollo" , aquí hay un granjero de código serio y de buen comportamiento.

---- Sea el blogger más educado y el programador más sólido ----

Trate de escribir cada artículo con cuidado y, por lo general, resuma las notas en actualizaciones automáticas ~

 

Supongo que te gusta

Origin blog.csdn.net/weixin_43787043/article/details/108506008
Recomendado
Clasificación