openjudge 1813 熄灯问题

描述
有一个由按钮组成的矩阵,其中每行有6个按钮,共5行。每个按钮的位置上有一盏灯。当按下一个按钮后,该按钮以及周围位置(上边、下边、左边、右边)的灯都会改变一次。即,如果灯原来是点亮的,就会被熄灭;如果灯原来是熄灭的,则会被点亮。在矩阵角上的按钮改变3盏灯的状态;在矩阵边上的按钮改变4盏灯的状态;其他的按钮改变5盏灯的状态。

在上图中,左边矩阵中用X标记的按钮表示被按下,右边的矩阵表示灯状态的改变。对矩阵中的每盏灯设置一个初始状态。请你按按钮,直至每一盏等都熄灭。与一盏灯毗邻的多个按钮被按下时,一个操作会抵消另一次操作的结果。在下图中,第2行第3、5列的按钮都被按下,因此第2行、第4列的灯的状态就不改变。
这里写图片描述
请你写一个程序,确定需要按下哪些按钮,恰好使得所有的灯都熄灭。根据上面的规则,我们知道1)第2次按下同一个按钮时,将抵消第1次按下时所产生的结果。因此,每个按钮最多只需要按下一次;2)各个按钮被按下的顺序对最终的结果没有影响;3)对第1行中每盏点亮的灯,按下第2行对应的按钮,就可以熄灭第1行的全部灯。如此重复下去,可以熄灭第1、2、3、4行的全部灯。同样,按下第1、2、3、4、5列的按钮,可以熄灭前5列的灯。
这里写图片描述

输入
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。0表示灯的初始状态是熄灭的,1表示灯的初始状态是点亮的。
输出
5行组成,每一行包括6个数字(0或1)。相邻两个数字之间用单个空格隔开。其中的1表示需要把对应的按钮按下,0则表示不需要按对应的按钮。
样例输入
0 1 1 0 1 0
1 0 0 1 1 1
0 0 1 0 0 1
1 0 0 1 0 1
0 1 1 1 0 0
样例输出
1 0 1 0 0 1
1 1 0 1 0 1
0 0 1 0 1 1
1 0 0 1 0 0
0 1 0 0 0 0
来源
1222

题解

枚举第一行熄灯的情况

如果上一行是1,则下一行是1

例:100101 下一行也是 100101 就可以关上一行的灯
这样推出后面几行

最后对最后一行进行判断

如果最后一行都是0就找到了答案 下面是代码

使用dfs搜索第一行的情况

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
bool a[10][10];//a记录开关灯情况
bool k[10][10];//原有的开关灯情况
bool c[10][10];//答案数组
void turn(int x,int y){//开关灯的函数
    a[x][y]=!a[x][y];
    if(x>=2) a[x-1][y]=!a[x-1][y];
    if(x<=4) a[x+1][y]=!a[x+1][y];
    if(y>=2) a[x][y-1]=!a[x][y-1];
    if(y<=5) a[x][y+1]=!a[x][y+1];
}
bool pd(){//判断是否成功关完
    for(int i=1;i<=6;i++)
        if(a[5][i]==1)
        return false;
    return true;
}
void deal(){//开关灯
    for(int i=1;i<=6;i++){
        if(c[1][i]==1)
        turn(1,i);
    }
    for(int i=2;i<=5;i++)
    {
        for(int j=1;j<=6;j++)
        if(a[i-1][j]==1)
        {
            c[i][j]=1;
            turn(i,j);
        }
    }
}
void makea(){//用a记下k
    for(int i=1;i<=5;i++)
        for(int j=1;j<=6;j++)
            a[i][j]=k[i][j];
}
void clean(){//如果失败 将c清零
    for(int i=2;i<=5;i++)
    for(int j=1;j<=6;j++){
        c[i][j]=0;
    }
}
bool flag=0;//判断是否成功
void dfs(int step){//搜索出c[1]的情况
    if(!flag){//成功就退出
        if(step==7){//找出一种c[1]就进行判断
            clean();//清空c
            makea();//用a记下k
            deal();//开关灯
            if(pd()){
                flag=1;
            }
            return;
        }
        for(int i=0;i<=1;i++)
        {
            c[1][step]=i;
            dfs(step+1);
            if(flag==1) return ;//成功就退出
        }
    }
}
int main()
{
    for(int i=1;i<=5;i++)
        for(int j=1;j<=6;j++)
            scanf("%d",&k[i][j]);
    dfs(1);//从1开始
    for(int i=1;i<=5;i++){//输出
        for(int j=1;j<=6;j++)
            cout<<c[i][j]<<" ";
        cout<<endl;
    }
    return 0;
}

update 2017 .6 .29

回档第一篇博客 又是一种心情

觉得那个时候好辣鸡的啊
连个dfs都想半天
学了高斯消元以后 重新来做此题 又花了好长时间(我是制杖吗?。。)
嗯 高斯消元的做法
首先:
1.这个点按奇数次和按一次是一样的 按偶数次和不按是一样的 因为按两次就又回去了
2.按的顺序和答案没有关系
列方程:
一个点最多被五个点影响(上下左右+自己) 因为每个点的次数只是0或者1 所以首先得到方程
像这个样子x1+x2+x3+x4+x5(mp[i][j]指i,j是否要改变)
但是mod2不是很好处理了
又想到 1和 1 变0 0和1 变1 那么这个时候用异或运算就好

x1^x2^x3^x4^x5=mp[i][j]
那么 此题是5*6的格子 需要30个未知数 列30个方程 解一下就行

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cmath>
using namespace std;
int n=5,m=6;int cnt=30;
int mp[50][50];
int a[50][50];
int dx[10]={0,1,-1,0,0};
int dy[10]={0,0,0,1,-1};
void gauss(){
    for(int i=1;i<=30;i++){
        int k=i;
        for(;k<=30;k++)
            if(a[k][i]!=0) break;
        swap(a[i],a[k]);
        for(int p=1;p<=30;p++){
            if(p!=i&&a[p][i])
            {
                for(int j=i;j<=31;j++)
                    a[p][j]=a[p][j]^a[i][j];//异或方程 
            }
        }
    }
}
int mk(int x,int y){
    return (x-1)*6+y;
}
bool in_Map(int x,int y){
    if(x<1||x>n||y<1||y>m) return false;
    return true;
}
void build(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int plc=mk(i,j);
            a[plc][31]=mp[i][j];//要得到的答案即要改变此点的次数 
            a[plc][plc]=1;
            for(int k=1;k<=4;k++){
                int tx=i+dx[k];
                int ty=j+dy[k];
                if(!in_Map(tx,ty)) continue;
                a[plc][mk(tx,ty)]=1;
            }
        }
}
int main(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&mp[i][j]);
    build();
    gauss();
    int cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            printf("%d ",a[++cnt][31]);
        printf("\n");
    }
    return 0;
}

附:poj1222=此题
代码如下咯:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cmath>
using namespace std;
int n=5,m=6;int cnt=30;
int mp[50][50];
int a[50][50];
int dx[10]={0,1,-1,0,0};
int dy[10]={0,0,0,1,-1};
void gauss(){
    for(int i=1;i<=30;i++){
        int k=i;
        for(;k<=30;k++)
            if(a[k][i]!=0) break;
        swap(a[i],a[k]);
        for(int p=1;p<=30;p++){
            if(p!=i&&a[p][i])
            {
                for(int j=i;j<=31;j++)
                    a[p][j]=a[p][j]^a[i][j];//异或方程 
            }
        }
    }
}
int mk(int x,int y){
    return (x-1)*6+y;
}
bool in_Map(int x,int y){
    if(x<1||x>n||y<1||y>m) return false;
    return true;
}
void build(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            int plc=mk(i,j);
            a[plc][31]=mp[i][j];//要得到的答案即要改变此点的次数 
            a[plc][plc]=1;
            for(int k=1;k<=4;k++){
                int tx=i+dx[k];
                int ty=j+dy[k];
                if(!in_Map(tx,ty)) continue;
                a[plc][mk(tx,ty)]=1;
            }
        }
}
int main(){
    int T;
    scanf("%d",&T);
    int nt=0;
    while(T--){
        printf("PUZZLE #%d\n",++nt);  
        memset(a,0,sizeof(a));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            scanf("%d",&mp[i][j]);
    build();
    gauss();
    int cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            printf("%d ",a[++cnt][31]);
        printf("\n");
    }
}
    return 0;
}