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