AcWing - 95 - 费解的开关(递推)

题目链接_(:з」∠)_
题目大意:每次点击一个地方周围4个点和自己的布尔值都会变成相反的数,问你没有办法在6步之内把一个5X5的方格全部变成1

这是lyd蓝书的一个递推的题目,那么自然与递推有关系啦!首先我们要明白两点:
1:每个地方只有点击奇数次有效,因为点偶数次相当于没点,当然最小的奇数次就是1次,多了也没意义。
2:每个点点击的顺序并不影响结果,这个应该比较好理解吧,因为每个点的变化次数只和它周围点(也可能是自己)的点击次数有关
  由前面两点我们能想到什么呢?我们可以来考虑每行的操作,对于一个5X5的方格图,我们想让它全变为1,要对第一行的若干位置点击1次(也可能是0个位置),对于第二行的若干位置点击1次,对第三行的若干位置点击1次...等等...这和我们说的递推有什么关系吗?
  假设我们知道第一行的点击方法,那么如果第一行还有0的话会怎么样呢?那么,它只能由第二行的点击来改变,因为第一行我们已经点过了,那么第两行的点法就确定了,那么如果第二行还有0呢?那一定是需要第三行的点击来改变的,因为第一行已经确定了,靠点击第二行来使第一行确定后的第二行剩下的0改变成1是行不通的,因为这样会影响到第一行。然后我们再看第三行、第四行,也是一样的,而第五行我们就不看了因为我们判断第四行的时候就已经推导出了第五行的点法,如果这时候再点击第五行,第四行就会再次改变。
  但是这里又有问题了,我们并不知道第一行要怎么点啊?没有关西!我们可以暴☆力出奇迹,只要把第一行所有的点法全都枚举一遍就行了。枚举的方法可以dfs也可以二进制枚举第一行一共五个数所以一共有\(2^5\)种方案,随后我们只要根据第一行的情况递推出来下面几行然后再判断一下点击次数和最后一行是否全为1就行了

//https://www.cnblogs.com/shuitiangong/
#include<set>
#include<map>
#include<list>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<climits>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define endl '\n'
#define rtl rt<<1
#define rtr rt<<1|1
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define maxx(a, b) (a > b ? a : b)
#define minn(a, b) (a < b ? a : b)
#define zero(a) memset(a, 0, sizeof(a))
#define INF(a) memset(a, 0x3f, sizeof(a))
#define IOS ios::sync_with_stdio(false)
#define _test printf("==================================================\n")
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<ll, ll> P2;
const double pi = acos(-1.0);
const double eps = 1e-7;
const ll MOD =  1000000007LL;
const int INF = 0x3f3f3f3f;
const int _NAN = -0x3f3f3f3f;
const double EULC = 0.5772156649015328;
const int NIL = -1;
template<typename T> void read(T &x){
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
const int maxn = 7;
int dx[] = {0, 0, 0, 1, -1};
int dy[] = {1, -1, 0, 0, 0};
int n = 5, ans;
char g[maxn][maxn], bk[maxn][maxn];
void turn(int x, int y) { //对当前格子以及四个方向的格子进行翻转
    for (int i = 0; i<n; ++i) {
        int xx = x+dx[i], yy = y+dy[i];
        if (xx>=0 && xx<n && yy>=0 && yy<n) {
            bk[xx][yy] ^= 1;
            ++ans;
        }   
    }    
}
int main(void) {
    int t;
    scanf("%d", &t);
    while(t--) {
        for(int i = 0; i<n; ++i)
            scanf("%s", g[i]);
        ans = INF;
        for (int k = 0; k<1<<n; ++k) { //二进制枚举第一个行所有点法
            memcpy(bk, g, sizeof(g)); //每次都要还原到最初状态
            int cnt = 0; bool flag = true;
            for (int i = 0; i<5; ++i)
                if (k>>i&1) { 
                    turn(0, i);
                    ++cnt;
                }
            for (int i = 0; i<n-1; ++i) //根据第一行的点法递推出后四行点法
                for (int j = 0; j<n; ++j)
                    if (~bk[i][j]&1) {
                        turn(i+1, j);
                        ++cnt;
                    }
            for (int i = 0; i<n; ++i) //检查最后一行是否全为1
                if (~bk[4][i]&1) flag = false;
            if (flag && cnt <= 6) ans = min(ans, cnt);
        }
        printf("%d\n", ans == INF ? -1 : ans);
    }
    return 0;
}
posted @ 2020-03-18 14:45  shuitiangong  阅读(185)  评论(0编辑  收藏  举报