JZOJ 3596. 【CQOI2014】和谐矩阵

17 篇文章 0 订阅
8 篇文章 0 订阅

Description

我们称一个由0和1组成的矩阵是和谐的,当且仅当每个元素都有偶数个相邻的1.一个元素相邻的元素包括它本身,及他上下左右的4个元素(如果存在)。

给定矩阵的行数和列数,请计算并输出一个和谐的矩阵。注意:所有元素为0的矩阵是不允许的。

Input

输入一行,包含两个空格分隔的整数m和n,分别表示矩阵的行数和列数。

Output

输出包含m行,每行n个空格分隔整数(0或1),为所求矩阵。测试数据保证有解。

Sample Input

4 4

Sample Output

0 1 0 0

1 1 1 0

0 0 0 1

1 1 0 1

Data Constraint

1<=m,n<=40

Solution

  • 首先,我们可以发现结论①:存在一种方案满足第一行沿中轴(竖着的)左右对称。

  • 因为左边满足条件则右边也会满足条件。

  • 接着,我们又可以发现结论②:一个方案,只要确定了第一行,接下来的每一个位置都唯一确定。

  • 因为我们可以通过上一行位置的1的奇偶性推出下一行。

  • 于是我们就可以先枚举第一行的一半( O(220) ),在翻折得到第一行。

  • 之后再往后推,看最后一行是否合法即可。找到一种方案就可以直接输出了。

  • 但是判断是否合法的时候如果是 O(N2) 判断的话,就可能会超时(主要看人品)。

  • 我们迫切要寻求更快的方法迅速推导!

  • 机智地考虑位运算。

  • 首先,我们先处理出第一行的异或值(即每个数周围的1的奇偶性),

  • 设为 num ,并将其压成一个长整型。

  • 那么我们要推出下一行的话,每一个数就要异或周围的四个数(包括自己)。

  • 于是就可以直接异或起来即可:

    num xor (num<<1) xor (num>>1) xor last

  • 左移一位、右移一位、自己、上一行(在草稿纸上画一画更好理解)。

  • 这样就处理出每个数异或周围的数之后的值了,还是 O(1) 的!

  • 注意不要忘了抹去左移后突出的一位( and 2m1 这个全 1 值即可)。

  • 最后判断最后一行的异或值是否为零即可,零则说明合法,可以直接输出。

  • 这样判断的时间复杂度就是 O(N) ,跑得飞快(位运算真强大)!

  • 总时间复杂度 O(N2n2) ,轻松通过本题。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=42,way[5][2]={{1,0},{0,1},{-1,0},{0,-1},{0,0}};
int n,m,h;
long long an;
int a[N],b[N],f[N][N],ans[N][N];
void dfs(int x)
{
    if(x>h)
    {
        long long num=0,last=0;
        memcpy(b,a,sizeof(b));
        for(int i=(m&1)?h-1:h,k=h;i;i--) b[++k]=a[i];
        for(int i=1;i<=m;i++)
        {
            num=num<<1|(b[i-1]^b[i]^b[i+1]);
            last=last<<1|b[i];
        }
        for(int i=2;i<=n;i++)
        {
            long long s=num;
            num=last^num^(num<<1&an)^(num>>1);
            last=s;
        }
        if(!num)
        {
            for(int i=1;i<=m;i++)
                if(ans[1][i]=b[i])
                    for(int j=0;j<=4;j++) f[1+way[j][0]][i+way[j][1]]++;
            for(int i=2;i<=n;i++)
                for(int j=1;j<=m;j++)
                    if(f[i-1][j]&1)
                    {
                        ans[i][j]=1;
                        for(int k=0;k<=4;k++) f[i+way[k][0]][j+way[k][1]]++;
                    }
            for(int i=1;i<=n;i++,printf("\n"))
                for(int j=1;j<=m;j++)
                    printf("%d ",ans[i][j]);
            exit(0);
        }
        return;
    }
    a[x]=1;
    dfs(x+1);
    a[x]=0;
    dfs(x+1);
}
int main()
{
    scanf("%d%d",&n,&m);
    h=(m+1)>>1,an=((long long)1<<m)-1;
    dfs(1);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值