算法学习之每日一题Day6

题目  飞行员兄弟

一、有关题目(涉及算法:枚举,位运算)

1.题目来源:《算法竞赛进阶指南》

2.题目链接 https://www.acwing.com/problem/content/118/

3.题目描述

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

二、抽象模型

这个题目是一个“开关问题”,与题目“费解的开关”很像。

(题目“费解的开关”具见文章 https://blog.csdn.net/m0_72955669/article/details/135901175

但仔细考虑会发现,这个题目每个“把手”的状态不会只受一个“开关”的影响,因此无法用递推来解决。

除此之外,本题与题目“费解的开关”最大的区别是,此题是4*4的矩阵而,题目“费解的开关”是5*5的矩阵。本题矩阵更小,也就意味着采用完全枚举的方法可能时间复杂度也是满足题目的。

因此,本题应采用枚举的方法来做。

本题属于指数型枚举,可以利用二进制与十进制的关系采用普通枚举法(可以利用位运算来对算法进行优化),也可以利用DFS(深度优先遍历)进行枚举。

三、相关代码

方法1:普通枚举法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

typedef pair<int, int> PII;

char g[5][5], backup[5][5];

vector<PII>ans, ans_b;

void turn_one(int x, int y){
    if(g[x][y] == '+') g[x][y] = '-';
    else g[x][y] = '+';
}

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

int main(){
    
    for(int i = 0; i < 4; ++ i){
        scanf("%s", g[i]);
    }
    
    memcpy(backup, g, sizeof g);
    
    for(int op = 0; op < 1<<16; ++ op){
        
        memcpy(g, backup, sizeof backup);
        ans_b.clear();
        
        for(int i = 0; i < 16; ++ i){
            if(op >> i & 1){
                int x = i / 4;
                int y = i % 4;
                ans_b.push_back({x, y});
                turn_all(x, y);
            }
        }
        
        bool has_closed = false;
        for(int i = 0; i < 4; ++ i){
            for(int j = 0; j < 4; ++ j){
                if(g[i][j] == '+'){
                   has_closed = true;
                   break;
                } 
            }
        }
        
        if(!has_closed){
            if(ans.empty() || ans.size() > ans_b.size()){
                ans = ans_b;
            }
        }
    }
    
    printf("%d\n", ans.size());
    for(int i = 0; i < ans.size(); ++ i){
        printf("%d %d\n", ans[i].first+1, ans[i].second+1);
    }
    
    return 0;
}

方法2:位运算优化方法1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

typedef pair<int, int> PII;

vector<PII> ans, ans_b;

char g[5][5];

int change[5][5];

int now, state;

int get(int x, int y){
    return x*4+y;
}

int main(){
    for(int i = 0; i < 4; ++ i){
        scanf("%s", g[i]);
    }
    
    for(int i = 0; i < 4; ++ i){
        for(int j = 0; j < 4; ++ j){
            for(int k = 0; k < 4; ++ k){
                change[i][j] += (1<<get(i, k)) + (1<<get(k, j));
            }
            change[i][j] -= 1<<get(i, j);
        }
    }
    
    for(int i = 0; i < 4; ++ i){
        for(int j = 0; j < 4; ++ j){
            if(g[i][j] == '+'){
                state += 1<<get(i, j);
            }
        }
    }
    
    for(int op = 0; op < 1<<16; ++ op){
        
        now = state;
        ans_b.clear();
        
        for(int i = 0; i < 4; ++ i){
            for(int j = 0; j < 4; ++ j){
                if(op >> get(i, j) & 1){
                ans_b.push_back({i, j});
                now ^= change[i][j];
            }  
          }
        }
        
        if(!now){
            if(ans.empty() || ans.size() > ans_b.size()){
                ans = ans_b;
            }
        }
    }
    
    printf("%d\n", ans.size());
    for(int i = 0; i < ans.size(); ++ i){
        printf("%d %d\n", ans[i].first+1, ans[i].second+1);
    }
    
    return 0;
}

方法3:DFS枚举法

//DFS(大多枚举的问题都可以用DFS来解决)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>

using namespace std;

typedef pair<int, int>PII;
vector<PII>temp, ans;

char g[5][5];

void turn_one(int x, int y){
    if(g[x][y] == '+') g[x][y] = '-';
    else g[x][y] = '+';
}

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

void DFS(int x, int y){
    if(x == 3 && y == 4){
        for(int i = 0; i < 4; ++ i){
            for(int j = 0; j < 4; ++ j){
                if(g[i][j] == '+')
                return;
            }
        }
        
        if(ans.empty() || ans.size() > temp.size()){
            ans = temp;
        }
        return;
    }
    
    if(y == 4){
        ++ x;
        y = 0;
    }
    
//指数型枚举
    // 按下开关(1)
    turn_all(x, y);
    temp.push_back({x, y});
    DFS(x, y+1);
    turn_all(x, y); //注意:这两句旨在恢复“现场”,因此不需要整体进行ans_b的清空以及数组g的备份。
    temp.pop_back(); //注意:这两句旨在恢复“现场”,因此不需要整体进行ans_b的清空以及数组g的备份。
    
    // 不按 (0)
    DFS(x, y+1);
    
}

int main(){
    for(int i = 0; i < 4; ++ i){
        scanf("%s", g[i]);
    }
    
    DFS(0, 0);
    
    printf("%d\n", ans.size());
    for(int i = 0; i < ans.size(); ++ i){
        printf("%d %d\n", ans[i].first+1, ans[i].second+1);
    }
    return 0;
}

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值