POJ 3279 Fliptile (搜索)

POJ 3279 Fliptile

题意

关灯问题,求最小的操作次数,并给出操作方式。

传送门

思路

注意到该问题有如下性质:
一旦第一行的操作状态确定后,其它所有的状态也就随之确定了。具体而言:
如果map[i][j]==1,那么为了把它变成0,我们只需要对flip[i+1][j]
进行一次操作,记录之。如此下去,
在最后一定可以把row-1行全部变成0.
所以再在最后判断一下最后一个row是不是全部是0就好了。
最后输出flip数组(ans)。

第一row的求法:详见代码

代码

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=16,maxm=16,inf=1e9;
int a[maxn][maxm],flip[maxn][maxm],ans[maxn][maxm];
int col,row;
int ok(int x,int y){
    if(x<0||x>=row||y<0||y>=col)return 0;
    return 1;
}
int xx[5]={0,0,1,-1,0};
int yy[5]={1,-1,0,0,0};
int check(int x,int y){
    int cnt=a[x][y];
    for(int i=0;i<5;i++){
        int nx=x+xx[i];
        int ny=y+yy[i];
        if(ok(nx,ny))cnt+=flip[nx][ny];
    }
    return cnt&1;
}
int calc(){
    for(int i=1;i<row;i++){
        for(int j=0;j<col;j++){
            if(check(i-1,j))
                flip[i][j]++;
        }
    }
    for(int i=0;i<col;i++)
        if(check(row-1,i))return 0;
    int cnt=0;
    for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            if(flip[i][j])cnt++;
    return cnt;
}
int main(){
//  freopen("DDD.in","r",stdin);
    scanf("%d%d",&row,&col);
    for(int i=0;i<row;i++)
        for(int j=0;j<col;j++)
            scanf("%d",&a[i][j]);

    /*for(int i=0;i<row;i++){
        for(int j=0;j<col;j++)
            printf("%d",a[i][j]);
        printf("\n");
    }*/

    int cnt=inf;
    for(int i=0;i<(1<<col);i++){//枚举从000...00到111...11
        memset(flip,0,sizeof flip);
        for(int j=0;j<col;j++){
            flip[0][col-j-1]=(i>>j)&1;//分离每一个状态
        }
        int num=calc();
        if(num<cnt&&num!=0){
            cnt=num;
            memcpy(ans,flip,sizeof flip);
        }
    }
    if(cnt==inf)printf("IMPOSSIBLE\n");
    else{
        for(int i=0;i<row;i++){
            printf("%d",ans[i][0]);
            for(int j=1;j<col;j++)
                printf(" %d",ans[i][j]);
            printf("\n");
        }
    }
    return 0;
}

后记

正确性何证?
一开始的对状态进行的搜索可以用位运算完成。
posted @ 2016-10-21 16:44  yohanlong  阅读(128)  评论(0编辑  收藏  举报