一、题目
1.题目背景
借助反作弊系统,一些在月赛有抄袭作弊行为的选手被抓出来了!
2.题目描述
现有 2n* 2n (n≤10) 名作弊者站成一个正方形方阵等候 kkksc03 的发落。kkksc03 决定赦免一些作弊者。他将正方形矩阵均分为 4 个更小的正方形矩阵,每个更小的矩阵的边长是原矩阵的一半。其中左上角那一个矩阵的所有作弊者都将得到赦免,剩下 3 个小矩阵中,每一个矩阵继续分为 4 个更小的矩阵,然后通过同样的方式赦免作弊者……直到矩阵无法再分下去为止。所有没有被赦免的作弊者都将被处以棕名处罚。
给出 nn,请输出每名作弊者的命运,其中 0 代表被赦免,1 代表不被赦免。
3.输入格式
一个整数 nn。
4.输出格式
2n* 2n 的 01 矩阵,代表每个人是否被赦免。数字之间有一个空格。
5.输入输出样例
3
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 1
0 0 0 0 0 1 0 1
0 0 0 0 1 1 1 1
0 0 0 1 0 0 0 1
0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1
1 1 1 1 1 1 1 1
二、心得与知识点
①以后一看到题目,不要立刻就用模拟的方法,而要先去分析实验的数据,看能否找到一定的规律
②不同的规律有不同的解法,比如说如下这一题
(1)规律1:对于这种01的图,要想到位操作的方法解答,另外,可以类比杨辉三角,就是考虑每一个元素与其上一行同一列的元素,和上一行不同列元素之间的是否有规律,比如这一题就是每一个数字都是它上方数字加上右上方数字再模2,也可看成异或。就是它上方的数字和右上方数字相同时为0,不同时为1
(2)规律2:找到为1的数据的纵横i与j,发现当为1时,必然有i|j=111(7),
所以,可以依据此来写
(3)规律3:发现除了左上角一个全是0矩阵,其它的三个都是一样的类型,因此可以采用递归,每次递归将左上方的正方形清零,并再次递归剩余的三个正方形,当正方形的边长为2时结束递归。
③知识点:C++中各种位操作https://blog.youkuaiyun.com/weixin_34284188/article/details/88016444
要特别注意左移与右移,比如int n=3;n=1<<n;那么结果n=8;因为这就是相当于把1左移3位,所以为1000,刚好就算8,符合题目中2^n的要求
三、源码
①规律1:
这题是个分形题,可以看出和杨辉三角很像。
所以顺带A了这个题:南蛮图腾
每一个数字都是它上方数字加上右上方数字再模2。
其实就是不进位加法,异或一下就好了。
代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#define re register
using namespace std;
int n;
int a[1234][1234];
int main()
{
scanf("%d",&n);
n = (1<<n);
a[0][n+1] = 1;
for(re int i=1;i<=n;++i)
{
for(re int j=1;j<=n;++j)
{
a[i][j] = a[i-1][j] ^ a[i-1][j+1];
printf("%d ",a[i][j]);
}
printf("\n");
}
return 0;
}
②规律2:
分析了一下样例:
000 001 010 011 100 101 110 111
000 0 0 0 0 0 0 0 1
001 0 0 0 0 0 0 1 1
010 0 0 0 0 0 1 0 1
011 0 0 0 0 1 1 1 1
100 0 0 0 1 0 0 0 1
101 0 0 1 1 0 0 1 1
110 0 1 0 1 0 1 0 1
111 1 1 1 1 1 1 1 1
首先判断是否赦免的函数一定是对称的
为啥?
假设这个函数是 f(i,j) (第i行,第j列)
那就有 f(i,j)=f(j,i)
所以不可能把f(i,j)简化到f’(i-j)或f’(i/j)
经试验也不可能简化成f’(i+j),f’(i*j)
由于i,j的取值在二进制上的位数都可以看做等于i,我们考虑用一下位运算
结果发现上图中 i|j=111(7)
所以有以下判断式:
f(i,j)=((i|j)!=((1<<n)-1)?0:1);
10行代码AC!
时间复杂度:Θ(4^n)
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
scanf("%d",&n);
for(int i=0;i<(1<<n);i++){
for(int j=0;j<(1<<n);j++){
printf("%d ",(i|j)!=((1<<n)-1)?0:1);}
printf("\n");}
return 0;}
③规律3:
本题基本思路是分治,代码可以通过递归来实现,每次递归将左上方的正方形清零,并再次递归剩余的三个正方形,当正方形的边长为2时结束递归。
#include<bits/stdc++.h>
using namespace std;
int n,p=1,a[1050][1050];
void di(int x,int l,int q) //x为正方形边长,l、q分别为递归正方形的横纵坐标
{
if(x==2) //递归边界
{
a[l][q]=0;
return;
}
for(int i=l; i<=l+x/2-1; i++)
for(int j=q; j<=q+x/2-1; j++)
a[i][j]=0; //将左上方的正方形清零
di(x/2,l+x/2,q);
di(x/2,l+x/2,q+x/2);
di(x/2,l,q+x/2); //此处是递归剩余的三个正方形
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
p*=2; //计算正方形的边长
for(int i=1; i<=p; i++)
for(int j=1; j<=p; j++)
a[i][j]=1; //将a数组先赋值为1
di(p,1,1); //开始递归
for(int i=1; i<=p; i++)
{
for(int j=1; j<=p-1; j++)
cout<<a[i][j]<<" ";
cout<<a[i][p]<<endl; //输出,此处可以避免输出行尾空格
}
return 0;
}