poj 2813 画家问题(枚举解决一维和二维)

来源:互联网 发布:淘宝实名制更改 编辑:程序博客网 时间:2024/05/19 15:20

给定一种状态集合,通常一个位置仅有一种状态,对其某一位置进行操作可改变其本身及其相邻位置的状态,通过一系列的操作达到目标状态集,并求出其所有可行操作中最少操作数的过程,可以将其归为画家问题。解决画家问题的核心是枚举每个位置的操作状态,并发现之间的联系,找出所有可行的解,然后找到最优解。
一维画家问题(特殊密码锁)
题目如下

思路分析:从前向后看,对于一个位置,要保证其前面一个一样,只能有一种按法。所以第一次的按法确定后,以后的按法也随之确定,再由最终最后位置是否相同即可判断是否可以。所以枚举第一个是否按,再找到最优解。

#include<iostream>#include<cstring>#include<algorithm>using namespace std;void reverse(char a[31], int i)//翻转{    if (a[i] - '0') a[i] = 0 + '0';    else a[i] = 1 + '0';}int main(){    bool flag = false;    char a[32], b[32],h[32];//h用来进行第二次操作,保持原型    memset(a, 0, sizeof(a));    memset(b, 0, sizeof(b));    cin.getline(a, 32);    cin.getline(b, 32);    int len = strlen(a);    int tmp1 = 0, tmp2 = 0;    for (int k = 0; k < 2; k++)//枚举第一个位置是否操作    {        memcpy(h, a, sizeof(a));        if (k)//如果按下        {            reverse(a, 0);            reverse(a, 1);//改变初始状态            tmp1++;            for (int i = 1; i < len; i++)            {                if (a[i - 1] != b[i - 1])                //前面不相等,为使前面相等,该位置翻转                {                    tmp1++;                    reverse(a, i);                    if (i < len - 1)                    {                        reverse(a, i + 1);                    }                }                if (a[len - 1] == b[len - 1]) flag = true;            }        }        else        {            for (int i = 1; i < len; i++)            {                if (h[i - 1] != b[i - 1]                {                    tmp2++;                    reverse(h, i);                    if (i < len - 1)                    {                        reverse(h, i + 1);                    }                }                if (h[len - 1] == b[len - 1]) flag = true;            }        }    }    if (flag)    {        tmp1 = min(tmp1, tmp2);        cout << tmp1 << endl;    }    else    {        cout << "impossible" << endl;    }    //system("pause");    return 0;}  

将其扩展到二维,即得到画家问题。(poj 2813)
题目如下
同样是枚举,不同的是空间由一维扩展到了二维,通过类比思想,枚举也应该由一个位置的状态扩展到一行的状态,可通过理解高中时的平面与空间的勾股定理。同样,第一行的状态集合确定了,以后各行的状态集合也确定了。难点在于,一行的状态如何表示,状态转换如何更简便的进行,考虑到其仅有两个状态,而二进制表示中也仅有0和1两种状态,所以可以采取位运算的方式来进行。

#include<iostream>#include<cstring>#include<algorithm>using namespace std;//白色记为1,黄色记为0inline void setbit(short &c, int i, bool v)//将状态转换成二进制表示{    if (v)    {        c |= (1 << i);    }    else    {        c &= ~(1 << i);    }}int onecount(short x)//记录1的个数,即所操作的次数{    int c;    for (c = 0; x>0; ++c)    {        x &= x - 1;    }    return c;}inline void Flip(short & x, int j)//状态转换{    x ^= (1 << j);}inline int getbit(short c, int i)//读取该位置的状态,是否涂色{    return (c >> i) & 1;}int main(){    short originlights[16];//初始输入    short lights[16];    int n;    cin >> n;    memset(originlights, 0, sizeof(originlights));    for (int i = 0; i<n; i++)    {        for (int j = 0; j<n; j++)        {            char c;            cin >> c;            setbit(originlights[i], j, c != 'y');        }    }    int sw = 1 << n;//第一行总共的变换情况    int ms;    short switchs;    int minDraw = 1 << 30;    for (ms = 0; ms<sw; ms++)    {        int totalDraw = 0;        switchs = ms;        memcpy(lights, originlights, sizeof(originlights));        for (int i = 0; i<n; i++)        {            totalDraw += onecount(switchs);            for (int j = 0; j<n; j++)            {                if (getbit(switchs, j))                {                    //本身的行转换                    if (j>0) Flip(lights[i], j - 1);                    Flip(lights[i], j);                    if (j<n - 1) Flip(lights[i], j + 1);                }            }            if (i<n - 1) lights[i + 1] ^= switchs;            //转换下一行,异或操作            switchs = lights[i];            //为使得本行全部置换,下一行置换状态与本行状态相同        }        if (lights[n - 1] == 0)        {            minDraw = min(minDraw, totalDraw);        }    }    if (minDraw == 1 << 30)    {        cout << "inf" << endl;    }    else        cout << minDraw << endl;    return 0;}

再次改变目标状态,分别得到两种目标,得到最优解,即为Flip Game(poj 1753),随后会在广搜中给出。
谢谢大家阅读,限于水平有限,代码效率低下,望各位巨巨指正。

0 0
原创粉丝点击