POJ2965的枚举解法和高效解法

Description

The game “The Pilots Brothers: following the stripy elephant” has a quest where a player needs to open a refrigerator.

There are 16 handles on the refrigerator door. Every handle can be in one of two states: open or closed. The refrigerator is open only when all handles are open. The handles are represented as a matrix 4х4. You can change the state of a handle in any location[i, j] (1 ≤ i, j ≤ 4). However, this also changes states of all handles in rowi and all handles in columnj.

The task is to determine the minimum number of handle switching necessary to open the refrigerator.

Input

The input contains four lines. Each of the four lines contains four characters describing the initial state of appropriate handles. A symbol “+” means that the handle is in closed state, whereas the symbol “−” means “open”. At least one of the handles is initially closed.

Output

The first line of the input contains N – the minimum number of switching. The rest N lines describe switching sequence. Each of the lines contains a row number and a column number of the matrix separated by one or more spaces. If there are several solutions, you may give any one of them.

Sample Input

-+--
----
----
-+--

Sample Output

6
1 1
1 3
1 4
4 1
4 3
4 4


这道题说起来并不是一道难题,我是把它当作枚举的练习题来做的。尽管这么说,提交了六七遍才能AC,实在是太丢人了。嘛,反正咱是来学习的,丢人不怕,学到了就是好的。

前面的规则说的挺复杂,其实就是说,有个4*4的0/1图,初始状况是随机的。每次可以选16个位置中的一个位置翻,每翻一次,该位置所在的行和列的所有值都被取反,问要至少翻几次才能把它翻成全是减号。

网上有人提出了高效的解法,感觉确实很牛X。不过既然是当枚举的练习,我就先说我这麻烦的枚举的解法吧。翻图可以用位操作来完成,区别就在于如何枚举和如何存储历史上。

第一步,最笨最笨的方法,每一步都遍历16种解法,深搜,递归,结果基本1秒的时间能算出7阶就不错不错的了,毫无疑问的TLE

第二步,停止了胡乱的尝试,开始分析如何减少运算量。首先我发现,最优的解法,一定不会有某一个位置被翻了超过一次。因为同一个位置,被翻两次,一定和没翻一样;而且,和翻的顺序也没有关系。其次,虽然我一开始就想到用一个16位的整数来表示图,并且用位操作来实现翻转,但是我最开始并没有想到,出现的不同的图的个数最多也不可能超过65535.如果最笨方法的最差运行时间是16的16次方的数量级,那如果用不同的图来表示,就可以去除其中绝大部分重复的运算和判断,把最差运行时间控制在65536的数量级。于是我想到了用set容器和map容器,分16级执行宽搜,每次搜到新的图的时候,就把新图插入map容器和记录该级出现过的数字的set容器,如果搜到了已经出现过的图,则停止这一分支的宽搜。搜索图的操作使用map的find操作进行。这样一来,尽管运算时间比最笨方法降了非常非常多,但是7阶运算基本就耗掉了1ms时间,继续TLE

第三步,痛定思痛定位第二步时候的问题。似乎最耗费时间的地方已经变成了容器的操作,尤其是find操作,怎么看怎么感觉多余。于是想到舍弃map容器,直接使用数组表示。定义了一个结构体,并定义了长度为65536的该结构体数组。每次搜到一个图,就把ifexist置为true,并记录下其上级和翻转位置。这样一来,map容器就被舍弃掉了

struct H{

bool ifexist;//是否已经出现过

unsigned short father;//上一级的数字,即由哪个图变化过来

char step;//变化过来的时候是在哪个位置发生的翻转

}

本机编译一下,感觉已经能满足要求了,一提交,还是TLE

第四步,想了想,这种环境下,使用容器确实有点不妥,不光是查找操作,插入操作也很耗时啊。既然可以用数组,那还是用数组吧,虽然这样用,占用的内存会增大一点。于是索性把记录每级出现的图的set容器也用数组实现,这下终于AC了

这里把枚举做法的最终代码贴出来,欢迎板砖!

#include <iostream>
using namespace std;
struct H{
    bool ifexist;
    unsigned short father;
    char step;
};
unsigned short update_map(unsigned short input,char location);
int main()
{
    unsigned short input=0,n=0;
    bool flag=false;
    for(int i=0;i<16;i++)
    {
        char c;
        std::cin >> c;
        if(c=='+')
        {
            input |= (1<<i);
        }
        else if(c!='-')
        {
            std::cout << "error" << std::endl;
        }
    }
    //std::cout << input << std::endl;
    unsigned short path[16][65536]={0};
    struct H hist[65536];
    for(int i=0;i<65536;i++)
    {
        hist[i].ifexist = false;
        hist[i].father = 0;
        hist[i].step = 0;
    }
    path[0][0]=input;
    for(int n=1;n<=16;n++)
    {
        //cout << "n" << n << endl;
        int k=0;
        for(int j=0;path[n-1][j]!=0;j++)
        {
            for(char c=0;c<16;c++)
            {
                unsigned short tmp = update_map(path[n-1][j],c);

                if(hist[tmp].ifexist)
                {
                    continue;
                }
                else
                {
                    if(tmp == 0)
                    {
                        cout << n << endl;
                        cout << (c/4+1) << " " << (c%4+1) << endl;
                        unsigned short res=path[n-1][j];
                        for(int k=n-1;k>0;k--)
                        {
                            char x=hist[res].step;
                            cout << (x/4+1) << " " << (x%4+1) << endl;
                            res= hist[res].father;
                        }
                        return 0;
                    }
                    hist[tmp].ifexist=true;
                    hist[tmp].father=path[n-1][j];
                    hist[tmp].step=c;
                    path[n][k]=tmp;
                    k++;
                }
            }
        }
    }
    cout << "no answer" << endl;
    return 0;
}
//location: where to switch the refrigerator,from 0 to 15
//i=location/4+1, j=location%4+1
unsigned short update_map(unsigned short input,char location)
{
    unsigned short tmp=0;
    switch(location)
    {
        case 0:
            tmp = 0x111f;break;
        case 1:
            tmp = 0x222f;break;
        case 2:
            tmp = 0x444f;break;
        case 3:
            tmp = 0x888f;break;
        case 4:
            tmp = 0x11f1;break;
        case 5:
            tmp = 0x22f2;break;
        case 6:
            tmp = 0x44f4;break;
        case 7:
            tmp = 0x88f8;break;
        case 8:
            tmp = 0x1f11;break;
        case 9:
            tmp = 0x2f22;break;
        case 10:
            tmp = 0x4f44;break;
        case 11:
            tmp = 0x8f88;break;
        case 12:
            tmp = 0xf111;break;
        case 13:
            tmp = 0xf222;break;
        case 14:
            tmp = 0xf444;break;
        case 15:
            tmp = 0xf888;break;
        default: break;
    }
    return input^tmp;
}


想了想,花了不到10分钟,就把高效解法的代码编出来了。思路也很清楚。如果要把某个+翻成-,把它所在的行和列的所有格都翻一遍就行了。那它本身翻7次,它所在行或列的单位被翻4次,其他位置的被翻2次。翻偶数次和没翻一样,翻7次和翻1次一样。那么定义一个16长的数组,对所有的+做统计,令其所在的行和列的元素都增1,最后再看一下,元素为1的位置就是被翻转过的位置,而元素为1的元素个数就是n的值。代码附上!

#include <iostream>
using namespace std;
int main()
{
    unsigned short n=0;
    bool flag=false;
    unsigned short input[16]={0};
    unsigned short times[16]={0};
    for(int i=0;i<16;i++)
    {
        char c;
        std::cin >> c;
        if(c=='+')
        {
            char row=i/4;
            char column=i%4;
            input[i]=1;
            times[row*4]++;
            times[row*4+1]++;
            times[row*4+2]++;
            times[row*4+3]++;
            times[column]++;
            times[4+column]++;
            times[8+column]++;
            times[12+column]++;
            times[row*4+column]--;   
        }
        else if(c!='-')
        {
            std::cout << "error" << std::endl;
        }
    }

    for(int i=0;i<16;i++)
    {
        if(times[i]%2)
        {
            n++;
        }
    }
    cout << n << endl;
    for(int i=0;i<16;i++)
    {
        if(times[i]%2)
        {
            cout << i/4+1 << " " << i%4+1 << endl;
        }
    }
    //std::cout << input << std::endl;
    

    return 0;
}


  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值