玩诈欺的小杉【推荐】

题目

是这样的,在小杉的面前有一个N行M列的棋盘,棋盘上有N*M个有黑白棋的棋子(一面为黑,一面为白),一开始都是白面朝上。
  小杉可以对任意一个格子进行至多一次的操作(最多进行N*M个操作),该操作使得与该格同列的上下各2个格子以及与该格同行的左右各1个格子以及该格子本身翻面。
  例如,对于一个5*5的棋盘,仅对第三行第三列的格子进行该操作,得到如下棋盘(0表示白面向上,1表示黑面向上)。

  00100
  00100
  01110
  00100
  00100

  对一个棋盘进行适当的操作,使得初始棋盘(都是白面朝上)变成已给出的目标棋盘的操作集合称作一个解法。
  小杉的任务是对给出的目标棋盘求出所有解法的总数。
Input

  每组测试数据的第一行有3个正整数,分别是N和M和T(1<=N,M<=20,1<=T<=5)
  接下来T个目标棋盘,每个目标棋盘N行,每行M个整数之前没有空格且非0即1,表示目标棋盘(0表示白面朝上,1表示黑面朝上)
两个目标棋盘之间有一个空行。
  特别地,对于30%的数据,有1<=N,M<=15
Output
对每组数据输出T行,每行一个整数,表示能使初始棋盘达到目标棋盘的解法总数

分析

之前我们曾做过一道类似的题目,叫做汽车嘉年华。
也是类似的一个按键可以使得四周发生改变(上下左右包括自己),不过当时的n比较小,才5
而且求的不一样是求最小的按键次数使得原来的状态变成目标状态(其实是一样复杂度,而且实现和想法也差不多)。
就是枚举最上面的一层,即第一行的选不选的状态,然后我们可以用 n2 遍历每一个点,
从左到右,若这层的这个数的上面不是目标状态,那么这个点必须要选。
同理我们可以推出每一行,而且我们在已知了第一行的同时我们在算的时候按照上面的策略我们是可以保证全部点(除了最后一行)一定是目标状态。
所以最后再判断最后一行的状态是否为目标状态即可,再统计选的数量。

好了,现在我们面对的问题比较麻烦,n,m<=20.
我们从头看起。
从数据来看,20*20=400,用状压来装是 2400 明显不行,n<=15也不行。
而且这个是上下的2格都变
所以我们换一个角度:是否能用汽车嘉年华的思路呢?
事实是可以的,但是汽车嘉年华是 2nn2t t为组数。
对于这题还是不够快。
所以我们可以试着考虑位运算,可是我们想若枚举第一列,那么接着我们如何将这一列的状态反映到后面下一列的状态呢?这是一个问题,这关系到了是否能离开n^2的思想
现在的问题是如何将已知的选不选的状态为x转化成下一列的选不选状态y。
而我在没看题解前没能自己解决。。
其实也很简单:
对于一列数的选不选,我们已经压成了now,上一列的选不选的状态为old.
就上面的问题,我们先从简到难,转化成选完之后下一列的是否改变的状态x。
x其实就等于f[i] xor now xor(now>>1) xor(now>>2) xor(now<<1)xor(now<<2)
f[i]为第i列的点转化成的数,为什么这个是对的呢?
首先一位xor 1就相当于将这个点翻一翻。
now中的点若有为1,那么f[i]对应的点就会翻一翻,也就是第一次的xor now,就可以将每个选的位置本身的值翻一翻。
而第二次的 xor(now>>1),即now 某个点若其为1,那么它可以影响这个点下一位的点的值,所以我们将now右移两位,便可以做到同一高度去xor了,这样便解决了不能快速更改那些选的点所改变的状态的问题了。
同理其他一样。
而最后now’=x xor old;
old是上一列的状态,上一列仍然可以影响一个下一列的格(即现在的一列)。
为什么这样就可以呢?这个不应是当前一列的状态吗?怎么是下一列的选不选状态呢?
因为答案要求要使这列为全0,所以每个有1的点都要抑或1,即下一列一定要让当前这列的状态为0,所以就是下一列的选不选的状态。

最后再判断最后一列的状态是否为0,然后累加即可。

这题我没想出来,是因为我没有往这个方面想,没有仔细分析问题是什么,从什么角度去做。这对解题很关键!继续加油,今天收获很多!

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int N=22;
int n,m,t,f[N];
char c;
int main(){
    for(scanf("%d %d %d",&n,&m,&t);t;t--){
            scanf("\n");
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    scanf("%c",&c);
                    if (i==1) f[j]=0;
                    f[j]+=(c-48)*1<<(i-1);
                }scanf("\n");
            }
            int ans=0;
            for(int j=0;j<=(1<<n)-1;j++){
                int now=j;
                int old=0;
                int t;
                for(int i=1;i<=m;i++){
                    t=now;
                    now=now^(now>>1)^(now>>2)^(now<<1)^(now<<2)^old^f[i];
                    old=t;
                    now=now&((1<<n)-1);
                }
                if (now==0) ans++;
            }
            printf("%d\n",ans);
        }
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值