【洛谷】【官方题单——函数与结构体】P5461 赦免战俘

一、题目

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.csdn.net/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;
}
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页