D - Fliptile 二进制枚举+反转

题目大意:让牛踩瓦片使得瓦片反转,但是因为牛蹄太大,在踩到要反转的瓦片的同时,也使相邻的上下左右四个瓦片反转。现在问,牛如何踩瓦片,才能使得踩的次数尽量少的前提下使瓦片全部反转过来。

思路:仔细想你会发现瓦片反转是有规律的,当第一行如何反转确定下来时,第二行会根据第一行的反转情况确定下来,比如第一行进行反转处理后 最终状态是1001,那么就要通过反转第二行的瓦片,使得第一行变为0000的状态,则第二行反转第一个和第四个。接着一次进行第三行、第四行、一直到n-1行。这里,为什么是n-1行呢,由于我们每次的反转(除第一行外)都是根据上一行的状态进行的,当反转完第n-1行时,第n行瓦片的状态就已经确定了。因此我们可以通过暴力枚举第一行瓦片的反转,通过判断最后一行是否符合要求,来判断第一行的这个枚举情况是否可行。 这里,枚举用到二进制枚举。

不懂二进制枚举的:传送门

下面上代码:

#include
#include
#include 
using namespace std;
int t[30][30], tem[30][30], m[30][30];//t表示瓦片状态,tem表示是否对该瓦片进行反转
int M,N,dir[5][2] = { 0,0,1,0,0,1,-1,0,0,-1 };
int get(int x, int y)//判断该瓦片状态,从而判断是否需要对它下面的这个瓦片进行反转
{
    int c = t[x][y];
    for (int i = 0; i < 5; i++)//由于该瓦片的周围四个方位对其都有影响,那么通过c来记录该瓦片的状态,若c为偶数,则表示白面朝上,若c为奇数,则表示黑面朝上。
    {
        int x1 = x + dir[i][0], y1 = y + dir[i][1];
        c += tem[x1][y1];
    }
    return c % 2;
}
int cal()//遍历计算反转瓦片的次数
{
    for (int i = 2; i <= M; i++)
        for (int j = 1; j <= N; j++)
            if (get(i - 1, j) == 1)
                tem[i][j] = 1;
    for (int i = 1; i <= N; i++)
        if (get(M, i))return -1;
    int res = 0;
    for (int i = 1; i <= M; i++)
        for (int j = 1; j <= N; j++)
            res += tem[i][j];
    return res;
}
int main()
{
    int min = -1;
    scanf("%d%d", &M, &N);
    for (int i = 1; i <= M; i++)
        for (int j = 1; j <= N; j++)
            scanf("%d", &t[i][j]);
    for (int i = 0; i < (1 << N); i++)
    {
        memset(tem, 0, sizeof(tem));
        for (int j = 1; j <= N; j++)
            tem[1][j] = (i >> (j - 1)) & 1;//二进制枚举第一行的所有可能
        int num = cal();
        if (num >= 0 && (min<0 || min>num))
        {
            min = num;
            memcpy(m, tem, sizeof(tem));
        }
    }
    if (min == -1)printf("IMPOSSIBLE\n");
    else
    {
        for (int i = 1; i <= M; i++)
            for (int j = 1; j <= N; j++)
                printf("%d%c", m[i][j], j == N ? '\n' : ' ');
    }
}

你可能感兴趣的:(搜索)