【题解&&海亮集训&&dp】 状态压缩dp 关灯问题II

40 篇文章 0 订阅
24 篇文章 1 订阅
题目传送门
题目描述:

现有n盏灯,以及m个按钮。每个按钮可以同时控制这n盏灯——按下了第i个按钮,对于所有的灯都有一个效果。按下i按钮对于第j盏灯,是下面3中效果之一:如果a[i][j]为1,那么当这盏灯开了的时候,把它关上,否则不管;如果为-1的话,如果这盏灯是关的,那么把它打开,否则也不管;如果是0,无论这灯是否开,都不管。
现在这些灯都是开的,给出所有开关对所有灯的控制效果,求问最少要按几下按钮才能全部关掉。


输入输出格式
输入格式:
前两行两个数,n m
接下来m行,每行n个数,a[i][j]表示第i个开关对第j个灯的效果。


输出格式:
一个整数,表示最少按按钮次数。如果没有任何办法使其全部关闭,输出-1


输入输出样例
输入样例#1:
3
2
1 0 1
-1 1 0

输出样例#1:
2


说明

对于20%数据,输出无解可以得分。

对于20%数据,n<=5

对于20%数据,m<=20

上面的数据点可能会重叠。

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


分析:

很多人看到这道题的取值范围后,第一时间的想法就是——搜索??

不过,这并不是搜索,而是一道状压dp,其实是否是状压dp其实很好判断,你只要看一看数据范围就可以了,状压的数据范围一般不会超过30,只要数据范围过了关,再看一眼题目是否像状压dp就可以了。

接下来看怎么做:

我们先预处理出t[i][j]表示第i个开关开j状态后的状态。将每一个状态,每一个开关打开后所对应得状态预处理出来.

预处理部分如下:

    for (int i=0;i<(1<<n);i++) 
      for (int j=1;j<=m;j++){
      	int ans=0;
	    for (int k=1;k<=n;k++){
		    if (a[j][k]==1) if (i&(1<<n-k)) ans=ans;else;//如果当前的操作是1,如果当前的灯是亮着的,把它关上,否则不管 
		    else if (a[j][k]==-1) ans=ans+(1<<n-k);//如果当前的操作是-1,直接加上 
		    else if (a[j][k]==0) if (i&(1<<n-k))ans+=(1<<n-k);else;else;//如果当前的操作是0,如果当前的灯是开着的,累加;否则不管 
		}
		t[j][i]=ans;
	}
预处理完之后,我们很容易知道题目的意思就是让我们求所有的灯都是0的状态,即让我们求0的时候的状态,因为开始时所有的灯都是开的,所以开始时的状态就是(1<<n)-1的状态。我们只要以(1<<n)-1为起点,0为终点做一遍BFS,记录步数即可

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[110][110];
queue < int > q;
int t[2001][2001];
int dp[2001];
int main(){
    scanf("%d %d",&n,&m);
    for (int i=1;i<=m;i++)
      for (int j=1;j<=n;j++) scanf("%d",&a[i][j]);
    for (int i=0;i<(1<<n);i++) 
      for (int j=1;j<=m;j++){
      	int ans=0;
        for (int k=1;k<=n;k++){
            if (a[j][k]==1) if (i&(1<<n-k)) ans=ans;else;//如果当前的操作是1,如果当前的灯是亮着的,把它关上,否则不管 
            else if (a[j][k]==-1) ans=ans+(1<<n-k);//如果当前的操作是-1,直接加上 
            else if (a[j][k]==0) if (i&(1<<n-k))ans+=(1<<n-k);else;else;//如果当前的操作是0,如果当前的灯是开着的,累加;否则不管 
        }
        t[j][i]=ans;
    }
//	for (int i=0;i<(1<<n);i++){
//	    for (int j=1;j<=m;j++) cout<<t[j][i]<<' ';
//	    cout<<endl;
//	}
    memset(dp,-1,sizeof(dp));
    dp[(1<<n)-1]=0;
    q.push((1<<n)-1);
    while (!q.empty()){
        int x=q.front();q.pop();
        for (int i=1;i<=m;i++){
            int y=t[i][x];
            if (dp[y]==-1) dp[y]=dp[x]+1,q.push(y);
        }
    }
//	for (int i=(1<<n)-1;i>=0;i--) cout<<dp[i]<<' ';
//	cout<<endl;
    printf("%d",dp[0]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值