AcWing 递归与递推

费解的开关

你玩过“拉灯”游戏吗?25盏灯排成一个5x5的方形。每一个灯都有一个开关,游戏者可以改变它的状态。每一步,游戏者可以改变某一个灯的状态。游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字“1”表示一盏开着的灯,用数字“0”表示关着的灯。下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在6步以内使所有的灯都变亮。

输入格式
第一行输入正整数n,代表数据中共有n个待解决的游戏初始状态。

以下若干行数据分为n组,每组数据有5行,每行5个字符。每组数据描述了一个游戏的初始状态。各组数据间用一个空行分隔。

输出格式
一共输出n行数据,每行有一个小于等于6的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若6步以内无法使所有灯变亮,则输出“-1”。

数据范围
0<n≤500
输入样例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1

思路:
通过思考,我们发现:
1:每一个灯最多按一次,按两次相当于没按。
2:每个灯的最终状态和操作顺序无关,只和能影响到他的灯被按的次数有关。
进一步观察,我们发现,如果第一排的状态确定了,而我们再想更改第一排的灯,只能通过按第二排的灯来操作。也就是,第i排的状态确定了,可是第i排依然还有灯是灭的,我们只能通过i+1排的灯来改变第i排灯的状态。
想到这里,我们可以枚举第一排的状态,然后看一下第一排是否有灯没亮,如果有,我们操作下一排的灯。以此类推。最后特判一下最后一排是否已经全部打开。如果是,那么更新一下步数,是否最小。
**注意:**枚举第一排的状态的时候,我们可以发现,一共2^5种情况,可以用0~31来表示这32种情况。利用位运算来实现每种操作。


import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int n;
    static char[][] ch = new char[5][5],temp=new char[5][5];
    static int[][] next = {{0,0},{-1,0},{0,1},{1,0},{0,-1}};
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        while((n--)>0){
            for(int i=0;i<5;i++){
                ch[i] = sc.next().toCharArray();
            }
            int min = 10;
            for(int op=0;op<32;op++){ //枚举第一行的所有情况
                for(int i=0;i<5;i++){
                    for(int j=0;j<5;j++){
                        temp[i][j]=ch[i][j];
                    }
                }
                int sum = 0;
                for(int i=0;i<5;i++){ //对第一行进行操作
                    if((op>>i&1)==1){
                        sum++;
                        ojbk(0,i);
                    }
                }
                for(int i=0;i<4;i++){
                    for(int j=0;j<5;j++){
                        if(ch[i][j]=='0'){
                            sum++;
                            ojbk(i+1,j);
                        }
                    }
                }
                boolean flag = false;
                for(int j=0;j<5;j++){
                    if(ch[4][j]=='0'){
                        flag = true;
                        break;
                    }
                }
                if(!flag){
                    min = Math.min(min,sum);
                }
                for(int i=0;i<5;i++){
                    for(int j=0;j<5;j++){
                        ch[i][j]=temp[i][j];
                    }
                }
            }
            if(min>6){
                System.out.println("-1 ");
            }else {
                System.out.println(min);
            }
        }
    }
    public static void ojbk(int x,int y){
        int tx,ty;
        for(int i=0;i<5;i++){
            tx = x+next[i][0];
            ty = y+next[i][1];
            if(tx<0||tx>=5||ty<0||ty>=5) continue;
            ch[tx][ty]^=1;
        }
    }
}

飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有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

思路:

1:每个位置我们最多可以操作一次,多次操作无意义。
2:每个把手的状态,与操作次数无关。只和能影响到他的把手的操作次数有关。
我们发现,能影响到一个点的点的数量非常多。我们不能用上一题的思路来解决这个题。而这题的数据量很小,我们可以通过暴力枚举的方式,来来进行操作,储存一下符合条件的最少操作次数。


import java.util.LinkedList;
import java.util.Scanner;

public class Main {
    static char[][] ch = new char[4][],t = new char[4][4];
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        for(int i=0;i<4;i++){
            ch[i] = sc.next().toCharArray();
        }
        LinkedList<Node> linkedList = new  LinkedList<>();
        for(int op=0;op<(1<<16);op++){//枚举是否翻某个点的每种情况
            LinkedList<Node>  temp = new LinkedList();
            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    t[i][j] = ch[i][j];
                }
            }
            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    int no =  no(i,j);
                    if((op>>no&1)==1){
                        ok(i,j);//翻
                        temp.addLast(new Node(i,j));
                    }
                }
            }
            if(ojbk()){
                if(linkedList.isEmpty()||linkedList.size()>temp.size()) linkedList = temp;
            }
            for(int i=0;i<4;i++){
                for(int j=0;j<4;j++){
                    ch[i][j] = t[i][j];
                }
            }
        }
        System.out.println(linkedList.size());
        for(Node n: linkedList){
            System.out.println(n);
        }
    }
    public static void nb(int x,int y){
        if(ch[x][y]=='+') ch[x][y] = '-';
        else ch[x][y] = '+';
    }
    public static void ok(int x,int y){
        for(int i=0;i<4;i++){
            nb(i,y); //翻ch[i][y]
            nb(x,i);//翻ch[x][i]
        }
        nb(x,y);
    }
    public static int no(int x,int y){
        return x*4+y;
    }
    public static boolean ojbk(){
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                if(ch[i][j]=='+'){
                    return false;
                }
            }
        }
        return true;
    }
}
class Node{
    int x,y;

    public Node(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return x+1+" "+(y+1);
    }
}

翻硬币

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是 ** oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作。

输入格式
两行等长的字符串,分别表示初始状态和要达到的目标状态。

输出格式
一个整数,表示最小操作步数

数据范围
输入字符串的长度均不超过100。
数据保证答案一定有解。

输入样例1:

**********
o****o****

输出样例1:
5
输入样例2:

*o**o***o***
*o***o**o***

输出样例2:
1
思路:每个硬币最多操作一次,能影响一个硬币状态的只有相邻的硬币。假如当前硬币不符合要求,那么我们翻他,统一一下操作次数。

import java.util.Scanner;

public class Main {
    static char[] ch1,ch2;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        ch1 = sc.next().toCharArray();
        ch2 = sc.next().toCharArray();
        int ans = 0;
        for(int i=0;i<ch1.length;i++){
            if(ch1[i]!=ch2[i]){
                ojbk(i);
                ojbk(i+1);
                ans++;
            }
        }
        System.out.println(ans);
    }
    public static void ojbk(int x){
        if(ch1[x]=='*') ch1[x]='o';
        else ch1[x]='*';
    }
}

原文地址:https://www.cnblogs.com/fxzemmm/p/14847923.html