XJOI 9777 关灯谜题 题解

题目描述:

有 n 盏灯和 m 个开关,每个开关可以操作若干盏灯。

操作有可能是关掉某个灯(1),如果那个灯开着,否则不管;

也可能是打开某个灯(-1),如果那个灯关着,否则不管。

现在灯全是开着的,请你求出最少多少次操作可以关掉所有灯。

输入格式:

第一行包含两个整数 n 和 m。

接下来 m 行,包含 n 个正整数 -1/0/1。1 表示关掉,-1 表示打开,0 表示不操作该盏灯。

输出格式:

输出一行一个整数,表示答案。

样例输入:

3
2
1 0 1
-1 1 0

样例输出:

2

数据规模:

对于100%数据 n<=10,m<=100

思路

  • 用状态压缩将每盏灯的开关压为一个数字(1表示开,0表示关)。

  • f_i 表示从初始状态(即 2^n-1 )状态到达状态i时的最少步数,因为是BFS,所以一开始搜到的即是最少的步数,一搜到0时便输出当前步数并退出,如果搜完了所有能搜到的状态也搜不到0便输出-1。

  • 对于每个状态,将每个开关都试一遍,如果得到了新的状态就推入队列。

  • 对于每个开关,如果 a_{i,j} 是1,就将该状态“与(&)”上 2^n-1-2^{j-1} ,如果是0就跳过,如果是-1,就将该状态“或(|)”上 2^{j-1}

剩下的就是bfs,没有什么注意事项

AC代码

#include<iostream>
#include<queue>
using namespace std;
int n,m,a[105][15];
queue<int> q;
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
        }
    }
    int dp[4096]; //最大2的(10+1)次方
    for(int i=0;i<=(1<<n)-1;i++) dp[i]=999999999;
    dp[(1<<n)-1]=0;
    q.push((1<<n)-1);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        for(int i=1;i<=m;i++){
            int x=now;
            for(int j=1;j<=n;j++){
                if(a[i][j]==0) continue;
                if(a[i][j]==1) x &= (1<<n)-1-(1<<(j-1));
                if(a[i][j]==-1) x |= 1<<(j-1);
            }
            if(x==0){
                cout<<dp[now]+1<<endl;
                return 0;
            }else if(dp[now]+1<dp[x]){
                dp[x]=dp[now]+1;
                q.push(x);
            }
        }
    }
    cout<<-1<<endl;
}
4 个赞