USTCOJ1240 黑屋 位运算

USTCOJ 1240,黑屋:http://acm.ustc.edu.cn/ustcoj/problem.php?id=1240

该题采用暴力枚举的方式,通过位运算加速开关灯操作。

若理解有困难,可移步“USTCOJ 1240 黑屋 非位运算版”http://blog.csdn.net/l03071344/article/details/8884790了解该解法基本思想。


百练相关题目为:http://poj.grids.cn/practice/1753。相关解法有:

http://www.cnblogs.com/HappyAngel/archive/2010/05/12/1734108.html
http://blog.chinaunix.net/uid-22263887-id-1778972.html
http://www.cnblogs.com/shuaiwhu/archive/2012/04/27/2474041.html
http://www.cppblog.com/Yusi-Xiao/archive/2009/03/21/77383.html

/********************************************
*原作者:scuwf。代码编号:53947
*代码完善及注释作者:baird。代码编号:71756
*第一次代码完善及注释作者:baird。代码编号:71756
*第二次代码完善及注释作者:Lance。代码编号:71824
********************************************/

#include <stdio.h>
#define MAX 150             //最大数据规模
#define MAX_CASE 0x8000     //最大测试数量,2 ^ 15
//此三个数组为全局变量,会自动初始化为全0。多出的一行的为了方便操作。
int lightmap[MAX+1], tempmap[MAX+1], switchstep[MAX_CASE];  
//i表示要变换的行的序号。该函数变换第i行中,k的二进制中标为1的位。
//以及这些对应位左侧的灯、右侧的灯、下方的灯。
void run_switch(int i, int kk, int m)
{
    tempmap[i] ^= kk;           //改变对应位置灯泡状态
    tempmap[i] ^= kk >> 1;      //改变对应位置右侧灯泡状态
    tempmap[i] ^= (kk << 1) & ((1 << m) - 1);//改变对应位置左侧灯泡状态
    tempmap[i + 1] = lightmap[i + 1] ^ kk;  //改变对应位置下方灯泡状态
}

//生成每一种行变换的所需的操作步骤
void count_switchstep(void)
{
    int i, t;
    for (i = 0; i < MAX_CASE; i++) //i代表行变换的编号。这里for循环记录了所有的行变换所需的
    {
        switchstep[i] = 0;         //初始化,switchstep[i]存储了i的二进制表示中1的个数
        t = i;
        while (t)
        {
            switchstep[i] += t & 1;
            t >>= 1;
        }
    }
}

int main(void)
{
    count_switchstep(); //生成每一种行变换的操作步骤
    int m, n, i, j, k, step, minstep;
    while (~scanf("%d%d", &n, &m))
    {
        minstep = -1; //定义每组测试数据的最小步骤并初始化为下确界
        for (i = 0; i < n; i++)
        {
            lightmap[i] = 0;
            for (j = 0; j < m; j++)
            {
                scanf("%d", &k);
                lightmap[i] += k << (m - 1 - j); //读入灯泡地图,用二进制的形式储存每一行的情况
            }
        }
        //暴力枚举第一行的所有可能的变换,一共有2 ^ m次方种变换。
        //例如k=1000 0000 0001。表示变换第一个和最后一个灯。
        //因为与1异或,相当于对改位取反;与0异或,该位不变。
        for (k = 0; k < 1 << m; k++) 
        {
            step = switchstep[k];       //对第一行进行该种变换所需步数
            tempmap[0] = lightmap[0];   //初始化第一行数据到testmap中
            run_switch(0, k, m);
            for (i = 1; i < n; i++)  //从第二行开始变换,每一行要保证上一行的灯全开(即全为1)。
            {
                //~testmap[i - 1] & ((1 << m) - 1)是由上一行灯泡情况而确定的本行的变换,
                //目的是要保证上一行灯全开
                int kk = ~tempmap[i - 1] & ((1 << m) - 1);
                step += switchstep[kk];   //累积在指定第一行变换下,将全部灯打开所需的步数
                run_switch(i, kk, m);
            }
            //通过考察最后一行是否全1,来判断本轮变换是否将所有灯都打开。
            //若条件成立,再且与当前最小步数进行比较
            //当minstep值为-1时,step < (unsigned)minstep总是成立。
            if (tempmap[n - 1] == (1 << m) - 1 && step < (unsigned)minstep)
                minstep = step;         
        }
        //打印结果
        if (minstep == -1)
            printf("no solution\n");
        else
            printf("%d\n", minstep);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值