题意:给你矩阵大小n×m,让你求出一个0/1矩阵,它的上下左右加上自己1的个数为偶数,且矩阵尽量不为全0。是多case。
条件:
- 上下左右加上自己1的个数为偶数
- 矩阵尽量不为全0
思路:会发现只要知道了第一行的排列那么就可以推出剩下所有行,这个时候很容易想到之前学的翻转问题,利用第一行状压枚举,但是会发现m的范围40,2^40暴力是不可能的。这时候会发现既然能够推出下一行,且n+1行全为0,这样就有一个关于第一行为未知数的方程组。这样虽然能够写出方程组,但是还要矩阵尽量不为全0。,所以我们在其中一个自由元放置为1就可以了,也可以把所有的自由元都赋为1。这题里面我把所有的自由元都赋为了1.
高斯消元:高斯消元主要用来求解线性方程组,也可以求解矩阵的秩、矩阵的逆。时间复杂度为n^3,主要与方程组的个数,未知数的个数有关。(建议先学习一下线性代数的知识)
原理:消元法。也就是我们常说的加减消元回带未知数。
高斯异或消元模板代码:
const int maxn=50;
int equ,var;//行数que,列数var+1(+1多1的地方是方程的b)
int a[maxn][maxn];//增广矩阵(我是从1开始的)
int x[maxn];//解集
int free_x[maxn];//标记是否为不确定的变量元(自由元)
int free_num;//自由元的个数
//返回1代表无解,返回0代表有解
int Gauss()
{
int max_r,col,k;//max_r表示当前这列绝对值最大的哪一行,col表示当前处理那一列
free_num=0;
for(k=1,col=1; k<=equ&&col<=var; k++,col++)
{
max_r=k;
//枚举当前处理的行数
//找到该col列元素绝对值最大的那行和第k行进行交换(为了在除法的时候减小误差)
for(int i=k+1; i<=equ; i++)
{
if(abs(a[i][col])>abs(a[max_r][col]))
max_r=i;
}
if(a[max_r][col]==0)
{
//说明第k行以下全部都为0,继续处理下一行
k--;
free_x[free_num++]=col;//记录自由元
continue;
}
if(max_r!=k)
{
//与第k行交换
for(int j=col; j<=var+1; j++)
swap(a[k][j],a[max_r][j]);
}
for(int i=k+1;i<=equ;i++){
//枚举要删去的行
if(a[i][col]!=0){
for(int j=col;j<=var+1;j++)
a[i][j]^=a[k][j];
}
}
}
//无解的情况,化简后的增广矩阵中存在(0, 0, ..., a)这样的列(a != 0).
for(int i=k;i<=equ;i++){
if(a[i][col]!=0)
return -1;
}
//自由元有var - k个,解不为一
if(k<var+1)
return var-k+1;
//解是唯一的
return 0;
}
题目代码:
#include <algorithm>
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn=50;
int equ,var;//行数,列数
int a[maxn][maxn];//增广矩阵
int x[maxn];//解集
//int free_x[maxn];//标记是否为不确定的变量元(自由元)
//int free_num;//自由元的个数
//返回1代表无解,返回0代表有解
int Gauss()
{
int max_r,col,k;//max_r表示当前这列绝对值最大的哪一行,col表示当前处理那一列
//free_num=0;
for(k=1,col=1; k<=equ&&col<=var; k++,col++)
{
max_r=k;
//枚举当前处理的行数
//找到该col列元素绝对值最大的那行和第k行进行交换(为了在除法的时候减小误差)
for(int i=k+1; i<=equ; i++)
{
if(abs(a[i][col])>abs(a[max_r][col]))
max_r=i;
}
if(a[max_r][col]==0)
{
//说明第k行以下全部都为0,继续处理下一行
k--;
//free_x[free_num++]=col;
x[col]=1;//自由元赋为1
continue;
}
if(max_r!=k)
{
//与第k行交换
for(int j=col; j<=var+1; j++)
swap(a[k][j],a[max_r][j]);
}
for(int i=k+1;i<=equ;i++){
//枚举要删去的行
if(a[i][col]!=0){
for(int j=col;j<=var+1;j++)
a[i][j]^=a[k][j];
}
}
}
//无解的情况,化简后的增广矩阵中存在(0, 0, ..., a)这样的列(a != 0).
for(int i=k;i<=equ;i++){
if(a[i][col]!=0)
return -1;
}
//自由元有var - k个,解不为一
if(k<var+1)
return var-k+1;
//解是唯一的
return 0;
}
long long int f[maxn][maxn];
void init(){
memset(a,0,sizeof(a));
memset(x,0,sizeof(x));
memset(f,0,sizeof(f));
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
init();
//状压每个第一行的编号
for(int i=1;i<=m;i++){
f[1][i]=(long long int)1<<i;
}
//然后异或每个点,看他们影响是来自于那几个第一行的
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i+1][j]=f[i][j-1]^f[i][j]^f[i][j+1]^f[i-1][j];
}
}
//形成增广矩阵
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
if(f[n+1][i]&((long long int)1<<j))
a[i][j]=1;
}
}
equ=m;var=m;//矩阵的行equ列var+1
//printf("sjdaslkf\n");//
int num=Gauss();
//-1无解的情况可以不用考虑,因为再怎么样都会有0000的情况是有解的
//解是唯一说明只能是00000的情况
//printf("%d\n",num);
if(num==-1){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("0 ");
}
printf("\n");
}
}
else {
for(int i=equ;i>0;i--){
if(!x[i]){
x[i]=a[i][var+1];
for(int j=i+1;j<=var;j++)
x[i]^=(a[i][j]&&x[j]);
}
f[1][i]=x[i];
}
for(int i=2;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=f[i-1][j-1]^f[i-1][j]^f[i-1][j+1]^f[i-2][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
printf("%lld ",f[i][j]);
}
printf("\n");
}
}
}
return 0;
}
总结:总是逃避一些题目,认为只有大佬才能写出来,所以高斯消元这类题之前碰到过却一直未尝试去解决。但这次尝试去学习了,其实也不是很难。面对困难还是不断努力去解决,不要过于害怕和否定自己。