分类:状压、暴力枚举
参考博客:
https://www.cnblogs.com/honeycat/p/5176211.html
代码是这位博主原创的,加了点注释
题目:
http://poj.org/problem?id=1753
1.
用16位的int型数a来表示棋盘的状态 ,简化问题
2.
用另一个16位int数b表示将要翻转的棋子。用a和b的异或运算表示翻转棋子的操作,异或运算的结果a^b =c表示翻转后的状态。
比如:翻转第9枚棋子
a=1010 0000 1101 1001
b=0000 1000 1100 1000
c=1010 1000 0001 0001
3.
***对于某一个棋子集合的翻转操作,最终的结果与这些棋子翻转的顺序无关,只与集合本身有关。
证明:异或运算符合交换律和结合律
***对于一个棋子,翻转0、2、4...偶数次的结果都一样。翻转1、3、5...奇数次的结果都一样。
证明:b^b =1 a^b^b = a
4.
这样,问题就变成:在一局游戏中,选择一个操作,在这个操作中,对于每一个棋子只有2个选择:翻/不翻。
共有C0/16+C1/16+C2/16+...C16/16种操作。
如果所有的可能操作都不能结束游戏,那么输出impossible。
如果可以结束游戏,选择翻棋子数目最小的一种操作,输出棋子数目。
#include
using namespace std;
const int inf = 0x7fffffff; //32-bit int的最大值,符号位为0,其他的都是1
char s[4][4];
int cs[16] = { 0x13,0x27,78,140,305,626,1252,2248,4880,8992,20032,35968,12544,29184,58368,51200 };//保存翻第i格子的变化值
int po[16] = { 1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768 };//保存2^i
int main()
{
int value = 0;
int cmin = inf;
char c;
for (int i = 0; i < 16; i++) //用16位的二进制数,表示状态
{
cin >> c;
if (c == 'b') //二进制的1代表black 0代表white 第i个数读到b 第i位就置1
value += (int)po[i]; // 0000 0000 0000 0001 ...
else
continue;
}
for (int i = 0; i < 65536; i++)
{//枚举所有状态
int cou = 0; //每次循环初始化最小次数
int cvalue = value; //初始状态
for (int j = 0; j < 16; j++)
if (i & (int)po[j]) //i枚举的是翻转方案的状态,在i的每一个二进制位上1代表翻0代表不翻,此句把这个状态翻译成要在棋盘上所做的操作, po[j] 的二进制形式,只有第j位为1 一
//依次检查i的第j位,若是1则执行下面语句,翻转第j个棋子
{
cou++;
cvalue ^= cs[j]; //异或赋值。如: a^=b相当于:a=a^b
//cs[j]中 翻动第j个棋子影响的区域在cs[j]的二进制中,表示为1 与1进行异或,cvalue中 1->0 0->1 实现翻转
}
// 最终状态与翻牌次序无关,只与待翻牌的集合有关,即:依次翻 4 10 13号牌的结果 与 依次翻13 10 4号牌的结果是相同的!
// 故只需要枚举所有的待翻牌组合即可, 上面for循环实现此功能
if (cvalue == 0 || cvalue == 65535)//全黑或全白
if (cou < cmin) //若本轮的状态下,所用次数小于当前最小值,
cmin = cou;
}
if (cmin == inf) cout << "Impossible\n";
else cout << cmin << endl;
system("pause");
return 0;
}