1. 程式人生 > >Fliptile POJ - 3279 (區域性列舉 + 狀態壓縮)

Fliptile POJ - 3279 (區域性列舉 + 狀態壓縮)

https://vjudge.net/problem/POJ-3279

參考部落格: https://blog.csdn.net/loy_184548/article/details/50949972

這道題目在vj上的kuangbin比賽中被歸類到了搜尋, 其實它還算不上搜素, 是一道比較基礎的列舉題目,和熄燈問題, 畫家問題,撥鍾問題是一類題, 如果單純用爆搜或者列舉來考慮的話可能會超時,複雜度O(2^N*M) 需要一點點小的技巧

首先, 我們來考慮一下, 改變當前一行的只有按下當前一行或者是上一行正對著的按鈕.

所以, 我們只用考慮第一行, 列舉第一行的開關操作, 然後與之對應的第二行就確定了, 第二行又確定了第三行...以此類推, 直到最後一行. 我們來判斷是否全部歸0

需要注意的是題中要求輸出最優解, 多個最優解又要求以字典序最小的輸出, 我在此用了一個字典序的狀態壓縮, 可以直接拿走下次用.

//Flip Tile 列舉
#include <stdio.h>
#include<cstring>
const int maxn = 20;
int M, N;
int color[maxn][maxn], cur[maxn][maxn];//題中讀入顏色, 當前操作顏色(防止直接改變源顏色)
int oper[maxn][maxn], ans[maxn][maxn], steps = 0, minSteps = 1<<30; //當前操作, 最小操作, 當前解和最小解
void press(int x, int y)
{ //按下x, y處的按鈕
    cur[x][y]^=1, cur[x+1][y]^=1, cur[x-1][y]^=1, cur[x][y+1]^=1, cur[x][y-1]^=1;
}
bool solve()
{ //判斷是否已經解決問題
    memcpy(cur, color, sizeof(color));
    //根據列舉結果改變第一二行
    for(int i = 1; i <= N; i++)
        if(oper[1][i])
            press(1, i), steps++;
    //根據第i-1行決定第i行的操作
    for(int i = 2; i <= M; i++){
        for(int j = 1; j <= N; j++)
            if(cur[i-1][j])
                oper[i][j]=1, press(i, j), steps++;
    }
     //判斷最後一行是否滿足條件
    for(int i = 1; i <= N; i++)
        if(cur[M][i]) return 0;
    return 1;
}
int main()
{
    scanf("%d%d",&M,&N);
    for(int i = 1; i <= M; i++)
        for(int j = 1; j <= N; j++)
            scanf("%d",&color[i][j]);
    //僅僅列舉第一行的狀態即可
    for(int i = 0; i < (1<<N); i++){ //狀態壓縮
        memset(oper, 0, sizeof(oper)), steps = 0; //初始化不要忘
        for(int j = 0; j < N; j++){
            oper[1][N-j] = (i>>j&1);
        }
        if(solve() && steps>0 && steps<minSteps)
            minSteps = steps, memcpy(ans, oper, sizeof(oper));;

    }
    if(minSteps < (1<<30))
        for(int i = 1; i <= M; i++){
            for(int j = 1; j <= N; j++)
                printf("%d ",ans[i][j]);
            printf("\n");
        }
    else printf("IMPOSSIBLE\n");
    return 0;
}