前言
虽然这是一道出现在高考模拟卷上的一道数学题,但咱们作为算法选手,看着这个熟悉的题目,不禁让人想起了被递推,深搜,二进制枚举,状态压缩dp支配的岁月,因此本文仅讨论算法知识,不讨论纯数学解法。
这道题第一眼看上去特别像 Acwing 95. 费解的开关
研究的都是一种状态通过有限次操作最后变成另一种状态的问题
眼前这道题如果用我们熟悉的语言可以如下描述
题目描述
已知一个3*3的矩阵
0 0 0
0 0 0
0 0 0
每一个格子代表一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
例如此时我们改变中间一个灯的状态,原图变为:
0 1 0
1 1 1
0 1 0
我们需要求一个最小操作步数k使得:
0 0 0 k次操作后 1 0 0
0 0 0 ------> 0 0 0
0 0 0 0 0 0
分析
很显然,本题数据范围很小,因此我们怎么做都行,如下我将采用两种做法。
1.深度优先遍历DFS 时间复杂度O(2^n)
#include <bits/stdc++.h>
using namespace std;
const int N = 5,INF = 0x3f3f3f3f;
int g[N][N],n,a[N][N];
bool st[N][N];
bool cheak()
{
a[0][0] = 1;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
if(a[i][j]!=g[i][j]) return false;
return true;
}
void turn(int x,int y) //对一个格子进行操作
{
g[x][y] ^= 1;
g[x-1][y] ^= 1;
g[x][y-1] ^= 1;
g[x+1][y] ^= 1;
g[x][y+1] ^= 1;
}
int ans = INF;
void dfs(int step)
{
if(cheak()) ans = min(ans,step); //每次更新最小答案
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(!st[i][j])
{
st[i][j] = true;
turn(i,j);
dfs(step+1);
st[i][j] = false;
}
}
int main()
{
cin>>n;
dfs(0);
cout<<ans<<endl;
return 0;
}
2.递推二进制枚举 时间复杂度O(2^n)
#include <bits/stdc++.h>
using namespace std;
const int N = 5;
int g[N][N],n,a[N][N];
bool cheak()
{
a[1][1] = 1;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
if(a[i][j]!=g[i][j]) return false;
return true;
}
void turn(int x,int y)
{
g[x][y] ^= 1;
g[x-1][y] ^= 1;
g[x][y-1] ^= 1;
g[x+1][y] ^= 1;
g[x][y+1] ^= 1;
}
int main()
{
cin>>n;
int ans = 0x3f3f3f3f;
int U = 1 << 9;
for(int op=0;op<U;op++)
{
memset(g,0,sizeof g);
int step = 0;
for(int i=0;i<9;i++)
if((op>>i)&1)
{
step++;
turn(i/3 + 1,i%3 + 1);
}
if(cheak()) ans = min(ans,step);
}
cout<<ans<<endl;
return 0;
}