题目链接:点击这里
初步思路
第一次看见这种问题,没有太多思路,就想直接枚举+剪枝。但如何剪枝呢?首先,如果直接枚举所有的情况,很明显是不可能的,若为 15∗15 15 ∗ 15 的矩阵,则需要枚举 215∗15 2 15 ∗ 15 种方法,实在是太大。因此剪枝成为了必要选择。可以想到,若我们已经确定了第一排的所有元素,则第二排也已经被确定了–因为第二排的元素将决定第一排的每个元素是否符合要求。这样,第二排元素也会确定第三排元素,直到所有元素都确定。
思路总结
经过Google,此种类型的问题被称为“开关问题”,就像多米诺骨牌一样,前面的会影响后面的,因此只要枚举最前面的所有可能情况即可,这样,此问题的复杂度被降到 O(2n∗n2) O ( 2 n ∗ n 2 ) ,由于此题目 n n 最多等于15,因此可以接受。
代码实现
此题的代码技巧性比较强,耐心看了好久才看懂。
技巧
- 此题需要对第一排的每个01元素进行枚举,可使用数的二进制形式来模拟枚举,降低运算量。
- 用“&”运算来表示奇偶性。
- 多使用<< >> 移位运算符来提高速度,也更直观。
- count的次数并不需要挨个计算,将最后算出的矩阵与原始矩阵进行对比即可。
注意点
此题只能将0变为1,不能将1变为0,这是判断条件。
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 20;
const int INF = 1<<30;
int dx[] = {0,0,-1};
int dy[] = {-1,1,0};
int a[N][N];
int tmp[N][N];
int ans;
int getSum(int x, int y, int n)
{
int ret = 0;
for(int i = 0; i < 3; i++)
{
/*只考虑前后上三个方向,因为是从前往后扫描,
下方向的可以根据前面的和进行更改*/
int tx = x + dx[i];
int ty = y + dy[i];
if(tx >= 1 && tx <= n && ty >= 1 && ty <= n)
ret += tmp[tx][ty];
}
return ret;
}
int check(int s, int n)
{
//先计算第一行
for(int i = 1; i <= n; i++)
{
//将s的二进制形式写入矩阵中
tmp[1][i] = s&1;
if(tmp[1][i] == 0 && a[1][i] == 1)
//若只能将1变为0才满足,则不合题意
return INF;
//移向下一位
s>>=1;
}
//依次计算后面的
for(int i = 2; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
int ret = getSum(i-1,j,n);
//计算前一行中三个方向的总和,若已经为偶数,则当前元素填0
tmp[i][j] = ret & 1;
if(tmp[i][j] == 0 && a[i][j] == 1)
//若只能将1变为0才满足,则不合题意
return INF;
}
}
int cnt = 0;
for(int i =1 ; i <= n; i++)
for(int j = 1; j <= n; j++)
//通过比较是否相同来计算次数
cnt += (a[i][j] != tmp[i][j]);
return cnt;
}
int main()
{
int t,n;
cin>>t;
for(int kcase = 1; kcase <= t; kcase++)
{
cin>>n;
for(int i=1; i<=n; ++i)
{
for(int j=1; j<=n; ++j)
{
cin>>a[i][j];
}
}
ans = INF;
for(int s=0; s<=(1<<n)-1; s++)
{
ans = min(ans,check(s,n));
}
if(ans == INF)
ans = -1;
cout<<"Case "<<kcase<<": "<<ans<<endl;
}
return 0;
}
最后,欢迎关注我的个人博客。