玩诈欺的小杉(详细解析+代码)

Description

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

  00100
  00100
  01110
  00100
  00100

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


Solution

先认真分析下题目

对一个点进行操作,他只对左右各一列,上下两列有影响。

又因为操作顺序对结果没有影响(显然)

所以可以考虑顺序从左到右搜下来

可以发现,上一列的状态是完全影响这一列的状态的

00100
00100
01110
00100
00100

假设当前搜到中间那个1,那么现在就必须进行操作,因为再往后已经没有其他的操作可以改变上一个1的状态了。也就是说,只要一个点左边的是1,那么这个点一定要进行操作。

所以完全可以枚举第0列(也就相当于枚举第1行如何操作,然后做一遍下来就好了,判断最后一行是否全为0,若是则累计入答案。

复杂度O(NMT*2^N)

这还有不小的常数,是过不了的。

考虑优化

我们知道,0 xor 1=1,1 xor 1=0

是不是很像我们的操作?

设上一列二进制状态为y1,当前列状态为y2

y2 xor y1,你想到了什么?

看看样例

00100
00100
01110
00100
00100

当前做到第3列,状态为y2,上一行状态是y1,y2 xor y1

那么状态就变成

00100
00100
01010
00100
00100

是不是改变了当前要改变的状态?

如果把y1向下拉一格,再xor

00100
00100
01010
00000
00100

你又发现了什么?

再拉一格

00100
00100
01010
00000
00000

如果再往上拉,把上面的状态同时改变

00000
00000
01010
00000
00000

再往右

00000
00000
01000
00000
00000

是不是都完成了?

同时,这样操作是把所有前一列的1都进行一次操作,一次做一整列

复杂度就变成了O(MT*2^N),可以在2s以内完成(平均为1s左右)

Code
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int a[22],n,m,t;
char c[22][22];
string st;
int main()
{   
    cin>>n>>m>>t;
    scanf("\n");
    while (t-->0)
    {
        int i,j;
        memset(a,0,sizeof(a));
        fo(i,1,n)
        {
            fo(j,1,m)
            {
                cin>>c[i][j];
            }
            scanf("\n");
        }
        int v;
        fo(j,1,m)
        {
            v=1;
            fo(i,1,n)//我是把整个图顺时针转了90度,更好操作
            {
                a[j]+=v*(c[i][j]-48);//二进制转换成十进制状态
                v*=2;
            }
        }
        int ans=0,k,b[21],al=(1<<n)-1;
        fo(a[0],0,al)
        {
            fo(i,0,m) b[i]=a[i];
            fo(i,1,m)
            {
                b[i]=(b[i]^(b[i-1]*2)^(b[i-1]*4)^(b[i-1]/4)^(b[i-1]/2)^b[i-1])&al; //操作当前的状态
                b[i+1]=(b[i+1]^b[i-1])&al;//操作下一状态
            }
            if ((b[m]&al)==0) 
            ans++;
        }
        cout<<ans<<endl;
    }
}

核心代码其实只有2行,比一个个暴力修改简洁多了。

其实很简单,不要把这题想的特别复杂而不敢去做,理解了这个就好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值