UVA.11464 Even Parity (思维题 开关问题)
UVA.11464 Even Parity (思维题 开关问题)
题目大意
给出一个n*n的01方格,现在要求将其中的一些0转换为1,使得每个方格的上下左右格子的数字和为偶数(如果存在的话),求使得最小的转换的个数。
最先想到的是枚举每个方格的状态,非0即1,那么就有2^(n*n)种情况,可见数量之大,必定超时。那么就必须要转换思路。
不难看出这是一个开关问题,就是说可以根据一行的数据,推算出下一行的数据,然后顺次推算出整个格子的数据,之后再来看改变了多少的01序列,求出最小的结果。(稍后会演示推算的算法)。
那么首先就是要枚举第一行的各种情况,这个不难实现。由于题目要求很局限,只是01序列,我们可以采用二进制的方法来依次枚举:
第一行有n个格子,每个格子有2种状态,故需要枚举2^n次。不妨我们先让n为3,看看是如何枚举的。
外层循环是 for i = 0 to (2^3-1)
i = 0 00000000
i = 1 00000001
i = 2 00000010
i = 3 00000011
i = 4 00000100
i = 5 00000101
i = 6 00000110
i = 7 00000111
不难看出 上面的式子中,后3位的情况,正好是我们要枚举的情况。但是我们不能直接取后三位,要想办法,于是问题抓换成:什么时候将某一位(二维数组的某一元素)赋值为1
由于一次只能对1位(二维数组中的一个元素)赋值,我们还需要一层循环, for j = 0 to (n-1) ,再观察下面的式子:
i = 0,j = 0 时 i&(1 << j) =00000000 即为 0
i = 0,j = 1 时 i&(1 << j) = 00000000 即为 0
i = 0,j = 2 时 i&(1 << j) = 00000000 即为0
上面的3式子,可以看做完成了000的赋值。
i = 1,j = 0 时 i&(1 << j) = 00000001 即为1
i = 1,j = 1 时 i&(1 << j) = 00000000 即为 0
i = 1,j = 2 时 i&(1 << j) = 00000000 即为 0
上面的3个式子,可以看做是完成了100的赋值
i = 2,j = 0 时 i&(1 << j) = 00000000 即为 0
i = 2,j = 1 时 i&(1 << j) = 00000000 即为 1
i = 1,j = 2 时 i&(1 << j) = 00000000 即为 0
上面的3个式子,可以看做是完成了010的赋值
……
以此类推,不难看出,这样的2重循环,就完成了对第一行格子的枚举。当然还要注意题目并不允许将1换成0,如果有这样的情况出现,那么要跳过。
现在完成了第一行的枚举。就要根据性质来分别计算第1到n-1行的数据了。依据的性质就是题目中的使得每个方格的上下左右格子的数字和为偶数(如果存在的话),有些读者到此处已经可以大致的写出程序了。大概的算法叙述如下:
我们要算第二行第一列的格子是0还是1,然是并不能直接对这个格子计算,因为他的右边我们还不确定。所以要根据他上一行的格子来计算,由于上一行的格子是在第一行,也就是说他没有上方和左方的格子(这里指的是第一行第一列的格子,若是中间部分的格子,那么只是没有上面的格子,若是第一行最右列的格子,那么他就没有右边和上边的格子),那么求和只需要求右面和下面的格子。而下面的格子又是待求的格子,那么只需要让这个和对2取模,就是这个格子的答案。当然依旧要注意题目并不允许将1换成0。如果有的话,还是要终止这次计算。
(若还有些许不明白,建议看这篇博文:传送门 里面介绍的十分详细)
最后分别求出剩下几行的格子,对比原先的数据,看看改变了多少个01序列。 别忘了上面我们写了一个for循环的枚举,在这么多的情况下,取最小即可,若没有则说明无法打成题目要求,输出-1即可。
代码总览
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nmax 20
#define INF 0x3f3f3f3f
using namespace std;
int a[nmax][nmax],b[nmax][nmax],n;
int check(int t)
{
memset(b,0,sizeof(b));
//根据传入的参数t来枚举第一行
for(int i = 0; i<n ;++i){
if(t & (1<<i) ) b[0][i] = 1;
else if(a[0][i] == 1) return INF;
}
//依次确定第1至n-1行的内容
for(int i = 1; i<n; ++i){
for(int j = 0; j<n; ++j){
int sum = 0;
if(i != 1) sum+=b[i-2][j];
if(j != 0) sum+=b[i-1][j-1];
if(j != n-1) sum+=b[i-1][j+1];
b[i][j] = sum % 2;
// 题目中不允许把1 变成 0
if(a[i][j] == 1 && b[i][j] == 0) return INF;
}
}
int ans = 0;
for(int i = 0; i<n; ++i)
for(int j = 0; j<n; ++j)
if(a[i][j] != b[i][j])
ans++;
return ans;
}
int main()
{
int T;
scanf("%d",&T);
for(int cas = 0; cas <T ;cas++){
//int n;
scanf("%d",&n);
for(int i = 0; i<n;++i)
for(int j = 0; j<n; ++j)
scanf("%d",&a[i][j]);
int ans = INF;
for(int i = 0 ; i < (1<<n);++i){
ans = min(ans, check(i));
}
printf("Case %d: ",cas+1);
if(ans == INF) printf("-1\n");
else printf("%d\n",ans);
}
return 0;
}