bzoj3503 [Cqoi2014]和谐矩阵(高斯消元)

83 篇文章 0 订阅
53 篇文章 0 订阅

bzoj3503 [Cqoi2014]和谐矩阵

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3503

题意:
我们称一个由0和1组成的矩阵是和谐的,当且仅当每个元素都有偶数个相邻的1。一个元素相邻的元素包括它本身,及他上下左右的4个元素(如果存在)。
给定矩阵的行数和列数,请计算并输出一个和谐的矩阵。注意:所有元素为0的矩阵是不允许的。

数据范围
1 <=m, n <=40

题解:
这是一个很有意思的题。

最初想法:
对于矩形的每一个位置都是一个未知数,由于相邻五个和除2余1,得到n*m个模方程。

转化一:
模方程并不好解。
由于每个位置只有0/1两种取值,模2为0就是异或为0,于是转化为n*m个xor方程。
这些方程就可以用高斯消元解,复杂度 O(n3m3)

转化二:
由 a[i][j-1]^a[i][j]^a[i][j+1]^a[i-1][j]^a[i+1][j]=0
令 i=i+1得:
a[i-1][j-1]^a[i-1][j]^a[i-1][j+1]^a[i-2][j]^a[i][j]=0
于是 a[i][j]=a[i-1][j-1]^a[i-1][j]^a[i-1][j+1]^a[i-2][j]
那么一个格子的数可以从上一排和上上排推下来,那么我们只需求出第一排的数即可。
那么每个格子的数都是由第一排的数异或而来。
因为推到n排就合法了,那么如果再推一排,第n+1排都是0,
那么第n+1排的数如果用第一排的数表示,就得到m个异或方程,
我们希望通过这m个异或方程(都是…=0的形式),解出第一排的数。

转化三:
那么现在需要求第n+1的这些数(都是0),如何用第一排的数表示(即异或方程系数)。
考虑到 每个数都是 a[i][j]=a[i-1][j-1]^a[i-1][j]^a[i-1][j+1]^a[i-2][j] 这样推下来的。
那么直接推就行了。
这里有个简便记录方法是用1<<(i-1)来表示第一排第i个数。各个位之间不会相互影响。
于是最后在n+1排数每个数二进制的1的位置即可。

于是我们高消求解第一排。
但是显然全部都是0是一个合法解,但我们要保证有1,于是任意解的数我们就让它为1。
这个过程消成上三角然后自下向上回代是很方便的。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=50;
LL a[N][N],b[N][N],c[N][N];
int pos[N];
int n,m;
void gauss()
{
    for(int i=1;i<=m;i++)
    {
        int k=-1;
        for(int j=1;j<=m;j++) if(!pos[j]&&a[j][i]){k=j;break;}
        if(k==-1) continue;
        for(int j=1;j<=m+1;j++) swap(a[i][j],a[k][j]);
        for(int j=1;j<=m;j++)
        if(j!=i&&a[j][i]) for(int k=1;k<=m+1;k++) a[j][k]^=a[i][k]; 
        pos[i]=i;
    }
    for(int i=m;i>=1;i--)
    {
        if(a[i][i]) c[1][i]=a[i][m+1]; else c[1][i]=1;
        if(c[1][i]) for(int j=i-1;j>=1;j--) if(a[j][i]) a[j][m+1]^=c[1][i];
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++) b[1][i]=1LL<<(i-1);
    for(int i=2;i<=n+1;i++) for(int j=1;j<=m;j++)
    b[i][j]=b[i-1][j-1]^b[i-1][j]^b[i-1][j+1]^b[i-2][j];
    for(int i=1;i<=m;i++)
    for(int j=1;j<=m;j++)
    if(b[n+1][i]&(1LL<<(j-1))) a[i][j]=1;
    else a[i][j]=0;
    gauss();
    for(int i=2;i<=n;i++) 
    for(int j=1;j<=m;j++)
    c[i][j]=c[i-1][j-1]^c[i-1][j]^c[i-1][j+1]^c[i-2][j];
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        printf("%lld",c[i][j]);
        if(j==m) printf("\n");
        else printf(" ");
    }
    return 0;
}

不知为何我消成对角线(28MS)比上三角(48MS)快

int pos[N];
void gauss()
{
    for(int i=1;i<=m;i++)
    {
        int k=-1;
        for(int j=1;j<=m;j++) if(!pos[j]&&a[j][i]){k=j;break;}
        if(k==-1) continue;
        for(int j=1;j<=m+1;j++) swap(a[i][j],a[k][j]);
        for(int j=1;j<=m;j++)
        if(j!=i&&a[j][i]) for(int k=1;k<=m+1;k++) a[j][k]^=a[i][k]; 
        pos[i]=i;
    }
    for(int i=m;i>=1;i--)
    {
        if(a[i][i]) c[1][i]=a[i][m+1]; else c[1][i]=1;
        if(c[1][i]) for(int j=i-1;j>=1;j--) if(a[j][i]) a[j][m+1]^=c[1][i];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值