Jeu de puzzle | Ressentez le charme des algorithmes

3. Jeux de réflexion

Grades dix horaire d'ouverture Lundi 7 septembre 2020 09:00
remise 0,8 Temps de remise Mardi 15 septembre 2020 09:00
Soumission tardive autorisée non Heure de fermeture Samedi 10 octobre 2020 23:00

La description

Xiao Zhang est un fan de escape room. Dans un jeu d'évasion, vous devez résoudre une série d'énigmes et enfin obtenir le mot de passe pour sortir. Maintenant, Xiao Zhang doit ouvrir une boîte contenant des indices, mais il y a une serrure à combinaison comme le montre l'image ci-dessous.

Chaque point est un bouton, et il y a une petite lumière à l'intérieur de chaque bouton. Comme le montre la figure ci-dessus, le rouge signifie que le voyant est allumé et le blanc signifie que le voyant est éteint. Chaque fois que vous appuyez sur le bouton, l'état de la lampe de ce bouton ainsi que de la lampe verticale et horizontale des quatre boutons de direction change (si les lumières sont éteintes, si les lumières sont éteintes). Si Xiao Zhang éteint les lumières en appuyant sur le bouton, il peut ouvrir la boîte.

Pour ce verrouillage à code, nous pouvons d'abord appuyer sur le bouton dans le coin supérieur gauche, et l'état de verrouillage du code passe à la figure suivante.

Appuyez ensuite sur le bouton dans le coin inférieur droit, et l'état de verrouillage du code passera à la figure suivante.

Appuyez enfin sur le bouton du milieu, les lumières sont toutes éteintes.

Maintenant, Xiao Zhang vous donne un statut de verrouillage de code, veuillez lui dire d'appuyer sur le bouton au moins plusieurs fois pour éteindre toutes les lumières.

Contribution

Deux entiers sur la première ligne n, m (1 \ leq n, m \ leq 16).

Dans la nligne suivante , chaque ligne a une mchaîne de caractères de longueur 01, 0 signifie que l'état initial de la lumière est éteint et 1 signifie que l'état initial de la lumière est allumé.

Production

Un entier sur une ligne signifie que toutes les lumières peuvent être éteintes en appuyant sur le bouton au moins plusieurs fois.

Remarques

Pour le premier exemple, consultez la description du titre, et pour le deuxième exemple, appuyez sur les boutons supérieur gauche et inférieur droit.

Le cas de test est garanti pour être résolu .

  Entrée de test Production attendue limite de temps Limite de mémoire Processus supplémentaire
Cas de test 1 Afficher sous forme de texte
  1. 3 3↵
  2. 100↵
  3. 010↵
  4. 001↵
Afficher sous forme de texte
  1. 3↵
1 seconde 64 millions 0
Cas de test 2 Afficher sous forme de texte
  1. 2 3↵
  2. 111↵
  3. 111↵
Afficher sous forme de texte
  1. 2↵
1 seconde 64 millions 0


       Pour cette question emmm, j'ai pensé à deux méthodes: la méthode d'élimination gaussienne ou la recherche en profondeur d'abord. Si vous êtes le premier contact, cela devrait être difficile à comprendre, mais peu importe si vous ne le comprenez pas complètement, lisez-le simplement quelques fois et assommez-le ...

        Parlons de la méthode de recherche en profondeur (dfs).

        Vous pouvez comprendre la recherche profonde comme ceci: énumérer tous les cas à tour de rôle , jusqu'à ce que l'énumération soit terminée, nous recherchons un résultat.


Idées

Pour cette question, pour trouver le nombre minimum de frappes, vous devez d'abord savoir:

  • Appuyez sur un bouton au plus une fois: s'il y a deux fois ou plus, il y aura un décalage, alors vos pas ne sont certainement pas les moindres.
  • L'ordre des touches n'a rien à voir avec le résultat final: les mêmes touches sont enfoncées dans un ordre différent, bien sûr, le résultat est le même.

       Si nous énumérons violemment : il y a deux cas pour chaque touche (appuyer ou ne pas bouger), alors nous devons énumérer la plupart du  2 ^ {16 \ ast 16}temps, ce qui est vraiment un nombre terrible ... alors il doit y avoir un meilleur algorithme. La raison de la réflexion ci-dessous emmm ne peut être qu'indicible, sans parler de l'idée, il suffit que vous puissiez comprendre cette méthode après l'avoir lue. Accédez directement à l' algorithme de base :

  1.  Tout d'abord, nous énumérons uniquement les frappes de la première rangée de lumières : nous devons ensuite énumérer la plupart du  2 ^ {16}temps, ce qui est toujours acceptable ... Enregistrez le nombre de fois que la première ligne est enfoncée pour chaque comptage d'énumération
  2. Pour les résultats de chaque énumération (la première rangée de lumières a été actionnée), nous les traiterons ensuite ligne par ligne . (Par exemple, si certaines lumières de la première rangée sont allumées, nous appuyons sur les lumières de la rangée suivante, de sorte que les lumières de la première rangée soient toutes éteintes en appuyant sur la deuxième rangée de boutons; ensuite nous discuterons de la deuxième rangée et utiliserons la troisième Le bouton de la ligne désactive la deuxième ligne ... et ainsi de suite). Si l'avant-dernière ligne est finalement corrigée, la dernière ligne a également complètement disparu, ce qui indique que cette énumération est faisable et que le nombre de frappes au clavier pendant tout le processus est enregistré; sinon, cette énumération n'est pas possible et nous devons rechercher d'autres éléments. Citez le programme.
  3. Pour une certaine énumération, alors l'opération ligne par ligne est complètement éteinte, cela signifie que nous l'avons complétée avec count + cur times. À ce stade, nous devons mettre à jour la valeur minimale de la réponse.

Code

        L'implémentation du code de cette question est plus compliquée, ce qui exerce la capacité de programmation. Si vous ne pouvez pas l'inventer, ne vous découragez pas trop, vous ne pouvez vous y familiariser que lentement. Il est également difficile de vous donner la possibilité de programmer une solution à un problème. Cette capacité est basée sur un peu de code de votre part , à droite ~ Mais pour vous fournir un code plus lisible pour votre référence. La partie très importante de l'apprentissage de la programmation est l'apprentissage. Code d'autrui ~

        Peut-être que vous écrivez encore tout le code dans la mauvaise fonction principale ( sinon, je ne l'ai pas dit ), la première étape pour améliorer la lisibilité du programme: écrire des fonctions par fonction.

        Tout d'abord, j'écrirai quelques modules de fonction à utiliser ( vous devriez pouvoir comprendre avec les commentaires, sinon, apprendre d'abord la syntaxe c / c ++ ), et les fonctions qui seront appelées plus tard. Les variables globales ont ces

#define MAX_LEN 17

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

1. Une fonction qui implémente l'opération XOR sur un entier

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

2. La fonction qui réalise le changement de la presse légère

/* 设定将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. Première recherche en profondeur (énumérer l'état du bouton de la première rangée de lumières)

         Indépendamment de la fonction calc appelée ici à la ligne 8, il vous suffit d'attendre que la fonction calc puisse calculer le nombre d'étapes suivantes pour une certaine énumération. Pour comprendre couche par couche, commencez par comprendre le fonctionnement de dfs, qui est le plus difficile à comprendre. Pour être une digression, j'étais très étourdi quand j'ai écrit le programme dfs pour la première fois ... dfs est essentiellement récursif, le secret de regarder le programme dfs est: regardez-le macroscopiquement, quand vous vous appelez récursivement, vous avez une macro compréhension de l'appel récursif ici En conséquence, il n'est pas nécessaire d'approfondir la fonction récursive. ( Emmmm, si vous ne comprenez pas cette phrase, vous la comprendrez tôt ou tard ... Après de nombreux battements récursifs complexes, vous reconnaîtrez beaucoup cette phrase )

     Par exemple, laissez-moi vous amener à comprendre les dfs suivants:

     Nous énumérons la sélection du bouton step-1 dans la première ligne: appuyez d'abord, puis considérez l'énumération du bouton suivant; restaurer (c'est-à-dire ne pas appuyer), puis considérer l'énumération du bouton suivant. Si vous pouvez comprendre cette idée à un niveau macro et que vous étudiez ensuite attentivement l'implémentation récursive de ce code, vous la comprendrez beaucoup plus rapidement. ( Si vous êtes un grand frère, traitez-le comme si je ne l'avais pas dit, si vous ne comprenez pas, vous le lirez encore quelques fois ).

/* 深度优先搜索,枚举第一排按键的所有按法
 * 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. Fonction Calc

        La fonction getCur () est appelée dans calc, principalement pour copier l'état du résultat de l'énumération dans le tableau cur, sinon il est difficile de restaurer l'opération directe sur le tableau a, ce qui affectera la discussion plus tard. L'idée de la fonction calc est la deuxième étape résumée dans notre algorithme, qui sort ligne par ligne.

/* 当第一排的灯被弄完后,把灯的状态复制到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;
}


Code complet

       D'accord ... Les ailes de poulet sont juste trop babysitting, et je dois démonter les fonctions une par une ... Posons le code complet ci-dessous, les grands peuvent venir voir le code complet. J'ai principalement écrit des dfs au début de la période de l'école primaire. J'ai peur que les lecteurs ne l'acceptent pas, alors vous pouvez penser que je suis très long. Peut-être que je résoudrai ce problème à 3 heures tard dans la nuit. C'est vraiment long ...

       Ce qui suit est le code complet, la fonction principale est principalement de traiter l'entrée. Faites particulièrement attention: pour aspirer les caractères de nouvelle ligne dans chaque ligne! Sinon ... vous ne savez pas pourquoi les ailes de poulet sont un problème jusque tard dans la nuit, car cela fait une journée entière parce que je n'ai pas sucé le caractère de nouvelle ligne à chaque fois ... J'ai vérifié divers endroits de recherche récursifs et profonds. , Du coup, mes larmes ont coulé lorsque le bug a été débogué à 2 heures ...

#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);
}


Bienvenue à faire attention au compte public personnel "  Chicken Wing Programming" , voici un agriculteur de code sérieux et bien élevé.

---- Soyez le blogueur le plus sage et le programmeur le plus solide ----

Essayez d'écrire chaque article avec soin et résumez généralement les notes en mises à jour push ~

 

Je suppose que tu aimes

Origine blog.csdn.net/weixin_43787043/article/details/108506008
conseillé
Classement