HDU 1882 Strange Billboard 状态压缩+简单位运算+枚举~

果然还是先用女神镇宅比较好~


进入正题:

原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=1882


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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值