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];
}
}