1. 程式人生 > >D - Fliptile 二進位制列舉+反轉

D - Fliptile 二進位制列舉+反轉

題目大意:讓牛踩瓦片使得瓦片反轉,但是因為牛蹄太大,在踩到要反轉的瓦片的同時,也使相鄰的上下左右四個瓦片反轉。現在問,牛如何踩瓦片,才能使得踩的次數儘量少的前提下使瓦片全部反轉過來。

思路:仔細想你會發現瓦片反轉是有規律的,當第一行如何反轉確定下來時,第二行會根據第一行的反轉情況確定下來,比如第一行進行反轉處理後 最終狀態是1001,那麼就要通過反轉第二行的瓦片,使得第一行變為0000的狀態,則第二行反轉第一個和第四個。接著一次進行第三行、第四行、一直到n-1行。這裡,為什麼是n-1行呢,由於我們每次的反轉(除第一行外)都是根據上一行的狀態進行的,當反轉完第n-1行時,第n行瓦片的狀態就已經確定了。因此我們可以通過暴力列舉第一行瓦片的反轉,通過判斷最後一行是否符合要求,來判斷第一行的這個列舉情況是否可行。 這裡,列舉用到二進位制列舉。

不懂二進位制列舉的:傳送門

下面上程式碼:

#include<stdio.h>
#include<string.h>
#include <iostream>
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' : ' ');
    }
}