1. 程式人生 > >POJ-3279 Fliptile 【狀態壓縮+DFS+列舉】

POJ-3279 Fliptile 【狀態壓縮+DFS+列舉】

題目傳送門

題目大意:有一個 M * N 的格子,每個格子可以翻轉正反面,它們有一面是黑色,另一面是白色。黑色翻轉之後變成白色,白色翻轉之後則變成黑色。遊戲要做的是把所有的格子翻轉為白色。不過每次翻轉一個格子,與它上下左右相鄰接的格子也會被翻轉。求總翻轉次數最少時,每個格子的翻轉次數。最少翻轉次數有多個時,輸出字典序最小的一組;解不存在的話,則輸出IMPOSSIBLE

題目樣例:0表示白色,1表示黑色

            1 0 0 1
            0 1 1 0
            0 1 1 0
            1 0 0 1

題目思路:
首先,列舉第一行的翻轉方法。此時,能翻轉(0,0)的只剩下了(1,0),所以可以直接判斷(1,0)是否需要翻轉。類似的(1,0)~(M-1,N-1)都能這樣判斷,如此反覆下去就能確定所有格子的翻轉方法,最後判斷(M-1,0)~(M-1,N-1)是否全為白色,若不是則意味著不存在可行的操作方法。像這樣,先確定第一行的翻轉方式,然後可以很容易判斷這樣是否存在解以及解的最步數是多少,這樣將第一行的所有翻轉方式都嘗試一次就能求出整個問題的最小步數。這個演算法中最上面 一行的翻轉方式共有2^N種
複雜度為O(M * N * 2^N)

AC程式碼:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <set>
using namespace std;
int M,N;
const int MAX=16;
int dx[5]={-1,0,0,0,1};
int dy[5]={0,-1,0,1,0};
int tile[MAX][MAX];//存放原始陣列
int opt[MAX][MAX];//儲存最優解
int file[MAX][MAX];//儲存當前解法
int get(int x,int y)//查詢當前第(x,y)個格子的顏色
{
    int c=tile[x][y];//自己本身的顏色
    for(int i=0;i<5;i++)//查詢周圍以及自己的翻轉次數,一個格子的翻轉次數等於它本身的翻轉次數加上它周圍格子的翻轉次數,因為它周圍的格子翻轉時它也會翻轉
    {
        int tx=x+dx[i];
        int ty=y+dy[i];
        if(tx>=0&&tx<M&&ty>=0&&ty<N)
            c+=file[tx][ty];
    }
    return c%2;//奇數為1(黑色),偶數為0(白色)
}
int cale()
{
    int res=0;
    for(int i=1;i<M;i++)
    {
        for(int j=0;j<N;j++)
        {
            if(get(i-1,j)==1)//如果當前第(i-1,j)個格子是黑色的話,必須翻轉這個格子
            {
                file[i][j]=1;
            }
        }
    }
    for(int i=0;i<N;i++)//判斷最後一行格子是否全為白色
    {
        if(get(M-1,i)==1)//若不是,則無解
            return -1;
    }
    for(int i=0;i<M;i++)//統計翻轉次數
    {
        for(int j=0;j<N;j++)
        {
            res+=file[i][j];
        }
    }
    return res;
}
void solve()
{
    int res=-1;
    //按字典序嘗試第一行的所有可能性
    for(int i=0;i<1<<N;i++)//i表示一個二進位制數,用來列舉第一行的各種不同翻法
    {
        memset(file,0,sizeof(file));
        for(int j=0;j<N;j++)
        {
            file[0][N-1-j]=(i>>j)&1;//每次取出最後一位存入file中,表示第一行的翻轉情況
        }
        int num=cale();//記錄每種翻法的翻轉次數
        if(num>=0&&(res==-1||res>num))//記下翻轉次數最少的翻法
        {
            res=num;
            memcpy(opt,file,sizeof(file));//將翻轉次數最少的翻法儲存在opt中
        }
    }
    if(res==-1)//無解
    {
        cout<<"IMPOSSIBLE"<<endl;
    }
    else
    {
        for(int i=0;i<M;i++)//輸出翻轉次數最少的翻法
        {
            for(int j=0;j<N;j++)
            {
                cout<<opt[i][j]<<" ";
            }
            cout<<endl;
        }
    }
    return ;
}
int main()
{
    cin>>M>>N;
    for(int i=0;i<M;i++)
    {
        for(int j=0;j<N;j++)
        {
            cin>>tile[i][j];
        }
    }
    solve();
    return 0;
}