/*
有一个M×N 的格子,每个格子可以翻转正反面,
它们一面是黑色,另一面是白色。黑色的格子翻转后就是白色,
白色的格子翻转过来则是黑色。游戏要做的就是把所有的格子都翻转成白色。
每次翻转一个格子时,与它上下左右相邻接的格子也会被翻转。
因为翻格子太麻烦了,所以想通过尽可能少的次数把所有格子都翻成白色。
现在给定了每个格子的颜色,请求出用最小步数完成时每个格子翻转的次数。
最小步数的解有多个时,输出字典序最小的一组。解不存在的话,
则输出IMPOSSIBLE。 范围M,N∈[1,15]
0代表白色 1代表黑色
EG:
输入:M=4 N=4
1001
0110
0110
1001
输出:
0000
1001
1001
0000
最少4次
首先,同一个格子翻转两次的话就会恢复原状,所以多次翻转是多余的。
联系POJ3276
翻转的格子的集合相同的话,其次序是无关紧要的。因此,总共有2^(N*M)种翻转的方法。
最左端的格子反转的方法只有1种
这回若考虑最左上角的格子
在这里,除了翻转(1,1)之外,翻转(1,2)和(2,1)也可以把这个格子翻转,
所以像POJ3276题目那样直接确定的办法行不通。不妨先指定好最上面一行的翻转方法。
此时能够翻转(1,1)的只剩下(2,1)了,所以可以直接判断(2,1)是否需要翻转。
类似地(2,1)~(2,N)都能这样判断,如此反复下去就可以确定所有格子的翻转方法。
最后(M,1)~(M,N)如果并非全为白色,
就意味着不存在可行的操作方法。像这样,先确定第一行的翻转方式 有2^N种反转方式,
然后可以很容易判断这样是否存在解以及解的最小步数是多
少,这样将第一行的所有翻转方式都尝试一次就能求出整个问题的最小步数。
复杂度O(M*N*2^N)。
代码:
*/
# include <stdio.h>
# define MAX 16
int FINA[MAX][MAX],DP[MAX][MAX],M,N;
char S[MAX][MAX]={"\0"};
int GET(char T[][MAX],int X,int Y);//计算(X,Y)的颜色
int JS();//计算第一行确定的情况下的最小操作次数
int gainchar(char *A,int min,int max);
const char x[6]={-1,0,0,0,1};//对上下左右的格子的坐标
const char y[6]={0,-1,0,1,0};
int main(){
int Flag=-1,i,j,k,m,Q;
do{
scanf("%d %d",&M,&N);
if(M<1||N<1||M>MAX-1||N>MAX-1)
printf("输入有误,重新输入 M,N∈[1,%d]:",MAX-1);
while(getchar()!='\n');
}while(M<1||N<1||M>MAX-1||N>MAX-1);
Q=1<<N; //Q为2^N次幂
for(i=0;i<M;i++)//输入
{
gainchar(S[i],N,N);
for(j=0;j<N;j++)//对S进行加工
S[i][j]-=48;
}
for(i=0;i<Q;i++)//确定第一行的方案 1--Q种
{
for(j=0;j<M;j++)
{
for(k=0;k<N;k++)
DP[j][k]=0;//每次枚举初始化
}
for(j=0;j<N;j++)
DP[0][N-j-1]=i>>j&1;//枚举i从0---Q枚举 将i以二进制存入DP[0][j]
m=JS();//计算出第一行确定的情况下的最小次数
if(m>=0&&(Flag>m||Flag<0))
{
Flag=m;//Flag存储最小次数
for(j=0;j<M;j++)
for(k=0;k<N;k++)
FINA[j][k]=DP[j][k];//将此次结果记录
}
}
if(Flag<0)
printf("IMPOSSIBLE\n");
else
{
printf("\n最少%d次\n",Flag);
for(i=0;i<M;i++)//输出的是次数 不是反转完之后的矩阵
{
for(j=0;j<N;j++)
printf("%d",FINA[i][j]);
printf("\n");
}
}
return 0;
}
int gainchar(char *A,int min,int max)//长度在[min,max] <闭区间> 之间时 函数结束 返回字符串A的长度
{
int B,C;//B保存A的长度 C记录缓冲区的字符串
do{
B=-1; C=0; //每次用户输入错误而循环时 都要初始化
fgets(A,max+1,stdin);//从键盘录入字符
while(A[++B]); //求A的字符串长度
if(A[B-1]!='\n')//如果最后一位不是'\n'说明其长度已超
while(getchar()!='\n') C++; //如果缓冲区里读入的不是\n则记录字符串数量
else //如果长度没超
A[--B]='\0'; //☆ 去掉\n 并且字符串长度-1
if(C||B&&B<min) //如果缓冲区有字符串或者长度不够 则提示错误 但如果用户只输入回车而且min!=0则不提示错误 但还要输入当前字符串
printf("您输入的字符串长度:%d字节\n请重新输入!\n注:只输入(%d--%d)个字节!\n",B+C,min,max);
}while(C||B<min);
return B;//返回B 即:A字符串的长度
}
int GET(char T[][MAX],int X,int Y)
{
int C=T[X][Y],i,xx,yy;
for(i=0;i<5;i++)
{
xx=X+x[i];
yy=Y+y[i];
if(xx>=0&&yy>=0&&xx<M&&yy<N)
C+=DP[xx][yy];//如果在范围内DP[][]里面存的是上一行和左边的格子的反转情况 并更新C
}//如果上面反转了5次 左边反转了4次 那么这个格子就是原来的T[X][Y]+4+5 偶数则为0(白) 奇数则为1(黑)
return C%2;// C&1 或C%2 运算用来判断是否奇数 奇数返回1 偶数返回0
}
int JS()//计算第一行确定的情况下的最小操作次数
{
int i,j,sum=0;
for(i=0;i<M-1;i++)
{
for(j=0;j<N;j++)
if(GET(S,i,j))//如果(i,j)为黑色
DP[i+1][j]=1;//反转下方格子
}
for(j=0;j<N;j++)
{
if(GET(S,M-1,j))//如果最后一行不满足情况
return -1;
}
for(i=0;i<M;i++)//如果满足 记录总步数
{
for(j=0;j<N;j++)
sum+=DP[i][j];
}
return sum; //返回总步数
}
附加小程序:
打印魔方阵:他的每一行每一列和对角线之和均相等,输入n(n为奇数)要求打印
从自然数到n^2的自然数构成的魔方阵。例如当a=3时,魔方阵为8 1 6
3 5 7
4 9 2
魔方阵的各数的排列规律为:
1:将1放在第一行的中间一列
2:从2开始直到n*n为止,各数按下列规则排放:
每一个数的存放的行比前一个数行数减一,列数加一
如果上一个数的行数为1则下一个数的行数为n,列数+1,如上述方阵中1在第一行第2列,则2应放在第三行第三列
当上一个数的列数为n时,下一个熟的列数为1,行数减一,如2在第三行第三列,3应在第二行第一列
3:如果按照上述方式确定的位置上已经有数或者上一个数是第一行第n列时则把下一个数放在上一个数的下面
# include <stdio.h>
# define N 16
int H=1,L; //Hang Lie
void main(){
int n,HH,LL,B[N][N]={0},k=1,j,i;
do{
printf("请输入n(n为奇数且n∈[3--%d]);",N-1);
scanf("%d",&n);
while(getchar()!='\n');
}while(!(n%2)||n>N-1&&n<3);//保证录入的为奇数且数组够用
L=n/2+1;
B[1][n/2+1]=1;
while(k++<n*n)
{
HH=(H-1)+n*(H==1);
LL=(L+1)-n*(L==n);
if((H==1&&L==n)||B[HH][LL])
{
HH=H+1;
LL=L;
}
B[HH][LL]=k;
H=HH;
L=LL;
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
printf("%03d ",B[i][j]);
printf("\n\n");
}
return 0;
}