洛谷P2622 关灯问题II 题解

洛谷P2622

tag:状态压缩

【题目大意】

n个灯,m个按钮,每个按钮都可以控制所有灯,给出每个按钮对每个灯的影响,求从全开到全关的最短步数。

【题目分析】

每盏灯只有两个状态,即开与关,记为0和1,则所有灯的状态总数为2^n(n=3时,有000,001,010,100……)

对每个状态单独分析,可以把每个开关看作一条有向边,这样把不同的状态连起来(也可能出现自环),因此,此题就变成了最短路问题,可以广搜解决。

接下来要考虑的是如何将按钮变成边,即如何确定经过按钮的操作,当前状态将转变成哪个状态。

回想二进制操作(与、或、非、抑或),经过尝试,我们发现“与”操作可以帮助我们解决关灯问题。比如:当原状态为100时(1表示开灯,0关),按钮1可以让1和3号灯关上,我们就可以令按钮1等于010.

100&010=000

“或”操作可以解决开灯问题,如:原状态为110,按钮2可以让1和3打开

110|101=111

当按钮3可以让1和3关上,让2打开时,就可以与010,或010,顺序不会影响结果。

【代码实现】

将二进制直接放到数组里?这是不现实的,所以还是用十进制来实现。

如何提取其中的一位呢?

a & (1<<j)

表示提取a的第j位,若结果是0,说明为0,若结果不为0,说明是1。

标程

#include<bits/stdc++.h>//2622
using namespace std;
int n , m , state[1200];//n<=10,所以前1023是有用的   表示到达i状态需要走的步数 
int trans[1200][105]; //trans[i][j]表示对状态i按下按钮j的状态 
int q[1200000],now;//q代表即将处理的队列 
int temp,open[110],close[110];
int main()
{
    int l=1,r=1; 
    scanf("%d%d", &n ,&m);
    for( int i = 1 ; i <= m ; i++)  close[i] = (1<<n)-1;//open[i]=0; 
    q[1] = (1<<n)-1;//初始状态:所有灯都开着,每一位都是1 
    for(int i = 0 ; i <= (1<<n) ; i++)  state[i]=-1;//所有状态初始化-1,表示之前没有遍历到这个状态 
    state[(1<<n)-1]=0;// 当前位置不需要移动就可以到达 
    for(int i = 1; i <= m ; i++)//m个按钮 
      for(int j =1 ; j <= n ; j++){//每个按钮对灯j的控制情况 
        scanf("%d", &temp);
        if( temp == 1 )  close[i] -= (1<<(j-1));//按钮i可以让灯j关上 
        if( temp == -1 ) open[i] += (1<<(j-1));//打开 
        }
    for( int i = 0 ; i < (1<<n); i++ )//枚举从000到111的所有状态 
        for( int j =1 ; j<= m ; j++)//枚举每一个按钮 
         trans[i][j] = ( i & close[j] ) | open[j];//经过按钮j,i会变成trans[i][j] 

    while( l <= r ){//当队列中还有任务时 
        if(state[0]!=-1) break;
        now=q[l];
        l++;
        for(int i = 1 ; i <= m ; i++){//对于每一个按钮 
            if( state[trans[now][i]] == -1 ){//如果没有到过这个状态 
                state[trans[now][i]] = state[now]+1;//这个状态可以由now状态走一步得到 
                q[++r] = trans[now][i];//准备处理这个状态 
            }
        } 
    }
    cout<<state[0]<<endl;//到0状态需要走的步数 
    return 0;
}

转载于:https://www.cnblogs.com/erutsiom/p/9904656.html

对于洛谷上的p1036题目,我们可以使用Python来解决。下面是一个可能的解法: ```python def dfs(nums, target, selected_nums, index, k, sum): if k == 0 and sum == target: return 1 if index >= len(nums) or k <= 0 or sum > target: return 0 count = 0 for i in range(index, len(nums)): count += dfs(nums, target, selected_nums + [nums[i]], i + 1, k - 1, sum + nums[i]) return count if __name__ == "__main__": n, k = map(int, input().split()) nums = list(map(int, input().split())) target = int(input()) print(dfs(nums, target, [], 0, k, 0)) ``` 在这个解法中,我们使用了深度优先搜索(DFS)来找到满足要求的数列。通过递归的方式,我们遍历了所有可能的数字组合,并统计满足条件的个数。 首先,我们从给定的n和k分别表示数字个数和需要选取的数字个数。然后,我们输入n个数字,并将它们存储在一个列表nums中。接下来,我们输入目标值target。 在dfs函数中,我们通过迭代index来选择数字,并更新选取的数字个数k和当前总和sum。如果k等于0且sum等于target,我们就找到了一个满足条件的组合,返回1。如果index超出了列表长度或者k小于等于0或者sum大于target,说明当前组合不满足要求,返回0。 在循环中,我们不断递归调用dfs函数,将选取的数字添加到selected_nums中,并将index和k更新为下一轮递归所需的值。最终,我们返回所有满足条件的组合个数。 最后,我们在主程序中读入输入,并调用dfs函数,并输出结果。 这是一种可能的解法,但不一定是最优解。你可以根据题目要求和测试数据进行调试和优化。希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值