1. 程式人生 > >HDU 1882 Strange Billboard 狀態壓縮+簡單位運算+列舉~

HDU 1882 Strange Billboard 狀態壓縮+簡單位運算+列舉~

果然還是先用女神鎮宅比較好~


進入正題:

Strange Billboard

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 877    Accepted Submission(s): 369


Problem Description The marketing and public-relations department of the Czech Technical University has designed a new reconfigurable mechanical Flip-Flop Bill-Board (FFBB). The billboard is a regular two-dimensional grid of R ×C square tiles made of plastic. Each plastic tile is white on one side and black on the other. The idea of the billboard is that you can create various pictures by flipping individual tiles over. Such billboards will hang above all entrances to the university and will be used to display simple pictures and advertise upcoming academic events.

To change pictures, each billboard is equipped with a ”reconfiguration device”. The device is just an ordinary long wooden stick that is used to tap the tiles. if you tap a tile, it flips over to the other side, i.e., it changes from white to black or vice versa. Do you agree this idea is very clever?

Unfortunately, the billboard makers did not realize one thing. The tiles are very close to each other and their sides touch. Whenever a tile is tapped, it takes all neighboring tiles with it and all of them flip over together. Therefore, if you want to change the color of a tile, all neighboring tiles change their color too. Neighboring tiles are those that touch each other with the whole side. All inner tiles have 4 neighbors, which means 5 tiles are flipped over when tapped. Border tiles have less neighbors, of course.



For example, if you have the billboard configuration shown in the left picture above and tap the tile marked with the cross, you will get the picture on the right. As you can see, the billboard reconfiguration is not so easy under these conditions. Your task is to find the fastest way to ”clear” the billboard, i.e., to flip all tiles to their white side.

Input The input consists of several billboard descriptions. Each description begins with a line containing two integer numbers R and C (1 ≤ R, C ≤ 16) specifying the billboard size. Then there are R lines, each containing C characters. The characters can be either an uppercase letter “X” (black) or a dot “.” (white). There is one empty line after each map.
The input is terminated by two zeros in place of the board size.
Output For each billboard, print one line containing the sentence “You have to tap T tiles.”, where T is the minimal possible number of taps needed to make all squares white. if the situation cannot be solved, output the string “Damaged billboard.” instead.

Sample Input 5 5 XX.XX X.X.X .XXX. X.X.X XX.XX 5 5 .XX.X ..... ..XXX ..X.X ..X.. 1 5 ...XX 5 5 ...X. ...XX .XX.. ..X.. ..... 8 9 ..XXXXX.. .X.....X. X..X.X..X X.......X X.X...X.X X..XXX..X .X.....X. ..XXXXX.. 0 0
Sample Output You have to tap 5 tiles. Damaged billboard. You have to tap 1 tiles. You have to tap 2 tiles. You have to tap 25 tiles.

這個題我想起了好小的時候玩的關燈遊戲,改變一盞燈的狀態(開或者關),相鄰的燈的狀態隨之改變(開變關,關變開)。

題意就是給你一個 r * c 的最開始的狀態,X代表黑, .  代表白。

翻轉(?)一張卡片(ps:大霧,其實我沒讀題,只是看了樣例,也不管是翻還是關了。英語不好傷不起)會引起相鄰卡片狀態的變化。

問,最少需要多少步,才能把這個 r * c 牌陣(想不到別的詞了T-T)翻轉成全部白色,也就是“ . ”

第一眼看到,每個格子只有兩種狀態,黑和白,所以想到了狀態壓縮,於是想到了狀壓DP。但是苦思冥想很久,沒想出狀態轉移方程。

但是仔細想想,用列舉會更加直觀簡便。狀態壓縮,解決記憶體問題,16*16應該不容易超時。於是決定開始動工。

首先,第一行的策略使用列舉:第一行不翻轉,翻轉第一張,翻轉第一第二張……翻轉全部,最壞情況要列舉16*16次。

第一行的狀態定下來之後,後面的策略也就唯一了!比如:

我們用1表示黑,0表示白

5 5

11011

10101

01110

10101

11011

列舉第一行的策略。首先,如果第一行一張都不翻。

那麼第一行的狀態就是:

11011

那麼在第二行,就必須要把上一行的黑牌翻轉。第三行的策略已經無法影響第一行了。

所以,第二行就要翻轉上一行1對應的位置的牌,使得上一行的牌翻轉。變成:↓

00000

10101

10101

10101

11011

同理,第三行的策略,也是要把第二行的1翻轉為0,的四行也是……直到最後一行,策略依然是把上一行的黑牌翻轉。

那麼方案可不可行的判斷條件就是,當最後一行把上一行的黑牌翻轉之後,自身也全變為白色,那麼就是可行的方案,否則不可行。之後取最小值方可。

然而當我理清思路,很歡快地碼完了程式碼,通過樣例一氣呵成,提交上去的時候,TLE了……

整個人都不好了……(最近一段時間總是TLE,心塞塞的)

狀態壓縮一般不會MLE,那麼時間問題上,我們可以作兩個優化,以保證不會超時。

優化1:在遞推的過程中,如果步數已經小於已有的最小答案了,那麼久不需要繼續遞推下去了。

優化2:當 r < c 的時候,我們可以把 r 和 c 翻轉,這樣所耗時間可以少很多(具體少多少我也不清楚,可以自己記下時對比一下)

下面貼上AC程式碼:

(ps:比賽現場寫的程式碼,很多地方不夠簡潔,累贅或者混亂,但是大致思路是沒問題的,還請大家多多指教~)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

int maps[20];
int kk[20];
void change(int r,int n,int p,int c);
int main()
{
    int c,r,n;
    char m[20][20];
    while(~scanf("%d%d",&r,&c))
    {
        if(r==0||c==0)
        {
            break;
        }
        getchar();
        if(r>=c)
        {
            for(int i=1; i<=r; i++)
            {
                maps[i]=0;
                scanf("%s",m[i]);
                for(int j=0; j<c; j++)
                {
                    if('X'==m[i][j])
                    {
                        maps[i]|=1<<j;//從大神那偷師的技巧!帥到炸~
                    }
                }
                getchar();
            }
        }
        else//優化2:如果r<c,反過來背面上他
        {
            memset(maps,0,sizeof(maps));
            for(int i=1; i<=r; i++)
            {
                scanf("%s",m[i]);
                for(int j=0; j<c; j++)
                {
                    if('X'==m[i][j])
                    {
                        maps[j+1]|=(1<<(i-1));
                    }
                }
                getchar();
            }
            //把r和c的兩值互換(ps:神一般的乾坤大挪移,從別的大神那學來的,裝逼神技!)
            r = r^c ;
            c = r^c ;
            r = r^c ;
        }
        n=1<<c;
        int minans=0x3f3f3f3f;//最大值
        for(int i=0; i<n; i++)//列舉第一行的所有翻法,之後的就被唯一確定下來了
        {
            int ans=0;
            for(int j=0; j<c; j++)
            {
                if((i&(1<<j))>0)
                {
                    ans++;
                }
            }
            for(int j=1; j<=r; j++)
            {
                kk[j]=maps[j];
            }
            change(1,i,r,c);
            //printf("%d\n",i);
            for(int j=2; j<=r; j++)
            {
                //printf("%d\n",kk[j-1]);
                for(int l=0; l<c; l++)
                {
                    if((kk[j-1]&(1<<l))>0)
                    {
                        ans++;
                    }
                }
                change(j,kk[j-1],r,c);

                if(ans>=minans)//優化1:如果已經大於最小的答案,那麼就不用繼續了
                {
                    //printf("no\n");
                    //printf("\n");
                    break;
                }
            }
            if(kk[r]==0&&ans<minans)//最後判斷最後一行有沒有全被艹翻過來
            {
                //printf("ok\n");
                //printf("%d\n",ans);
                minans=ans;
            }
            //printf("\n");
        }
        //如果值沒變,說明沒有答案。
        //(每個格子都翻一遍也只是16^2,然而這是不可能的,因為每個格子翻一遍,就跟原來一樣了)
        if(minans!=0x3f3f3f3f)
            printf("You have to tap %d tiles.\n",minans);
        else
            printf("Damaged billboard.\n");
    }
    return 0;
}

void change(int r,int n,int p,int c)//r:當前行;n:操作;p:總行數;c:總列數
{
    for(int i=0; i<c; i++)
    {
        if((n&(1<<i))>0)
        {
            //注意判斷越界!
            if(r>1)
            {
                kk[r-1]=kk[r]^(1<<i);//翻轉上一行該位
            }
            kk[r]=kk[r]^(1<<i);//翻轉此行該位
            if(i>0)
            {
                kk[r]=kk[r]^(1<<(i-1));//翻轉此行左一位
            }
            if(i<c-1)
            {
                kk[r]=kk[r]^(1<<(i+1));//翻轉此行右一位
            }
            if(r<p)
            {
                kk[r+1]=kk[r+1]^(1<<i);//翻轉下一行此位
            }
        }
    }
    return;
}
到此為止,歡迎交流。

最後再附一張女神照~


10101

11011