数学一本通6.2 数字方阵
定义
其实数字方阵并不是一种数学工具(?
可以当做不用运算的、行数=列数的 矩阵。
从语文的角度看:
矩形->矩阵 长方形的
(正)方形->方阵 正方形的
???
性质
主要总结几种基本的变换
1.垂直对称 $f'(i,j)=f(i,n+1-j)$
2.水平对称 $f'(i,j)=f(n+1-i,j)$
3.对角线(左上-右下)对称 $f'(i,j)=f(j,i)$
4.对角线(左下-右上)对称 $f'(i,j)=f(n+1-j,n+1-i)$ //原文貌似有误
5.中心对称(水平对称+垂直对称) $f'(i,j)=f(n+1-i,n+1-j)$
6.顺时针$90$度(水平对称+对角线对称) $f'(i,j)=f(n+1-j,i)$
7.顺时针$90$4度(垂直对称+对角线对称) $f'(i,j)=f(j,n+1-i)$
都易证
为什么我觉得会出锅
例题
主要部分。这算是全书最水的部分了……全是模(%)拟
方法:模拟法、归纳法。
例6.2-1 (NOIp2015 提高组)n阶奇数幻方/神奇(jī)的幻方
(https://www.luogu.org/problemnew/show/P2615)
$n$为奇数时,输出$n$阶幻方。
分析题目中的四个变换方式:
1.若(K-1)在第一行但不在最后一列,则将K填在最后一行,(K-1)所在列的右一列
2.若(K-1)在最后一列但不在第一行,则将K填在第一列,(K-1)所在行的上一行
3.若(K-1)在第一行最后一列,则将K填在(K-1)的正下方;
4.若(K-1)既不在第一行,也最后一列,如果(K-1)的右上方还未填数,则将K填在(K-1)的右上方,否则将K填在(K-1)的正下方。
???规律很明显???
综上所述,可得:
①当当前位置的右上一格为空时 填进去
②当……不为空时 填在下面(在$k<n^2$范围内肯定为空,别问我我也不会证)
(但是其实有一种玄学反证:如果不为空,题目出错,所以为空)
(这个说法我自己都不信,但是考试时还是很好用的 。。。)
代码:
#include<iostream>
#include<iomanip>
using namespace std;
int n,x,y,a[55][55];
int main() {
ios::sync_with_stdio(false);
cin>>n;
y=n/2;
for(int i=1;i<=n*n;i++) {
a[x][y]=i;
x--,y++;
x+=n,y+=n;
x%=n,y%=n;
if(a[x][y]) x+=2,y--;
x+=n,y+=n;
x%=n,y%=n;
}
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++)
cout<<a[i][j]<<' ';
cout<<endl;
}
return 0;
}
例6.2-2 N阶斜线方阵
给出$n$,输出如图所示的一个$n^2$方阵
1 2 4 7 11
3 5 8 12 16
6 9 13 17 20
10 14 18 21 23
15 19 22 24 25
首先可以按照斜线填数。
然后注意到,相同斜线上的数横纵坐标之和相等。
代码:
#include<iostream>
#include<iomanip>
using namespace std;
int n,count=1,a[25][25];
int main() {
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++) {
for(int j=1;j<=i;j++) {
a[j][i+1-j]=count++;
}
}
for(int i=n-1;i>=1;i--) {
for(int j=1;j<=i;j++) {
a[n-i+j][n-j+1]=count++;
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++)
cout<<setw(3)<<a[i][j]<<' ';
cout<<endl;
}
return 0;
}
例6.2-2 (NOIp2014 普及组)N阶螺旋方阵/螺旋矩阵
(我记得$17$年刚学$OI$的时候有一次比赛,这题是第二题。然后我调了一个小时发现数据太大没法模拟……)
用实验法 可以得出此题应该用归纳法,即数学推理法。模拟是不行的。
由于这是正方形,可以像靶形数独像箭靶一样,将方格分层,再处理一层的环形。
首先设左上角数字为$k$
- 第一行:显而易见可得$f(i,1)=i+k-1$;
- 最后一行:右下角为$k+2n-2$,$f(i,n)=(k+2n-2)+(n-i)=k+3n-i-2$
- 第一列:左下角为$k+3n-3$,$f(1,i)=(k+3n-3)+(n-i)=k+4n-i-3$
- 最后一列:右上角为$k+n$,$f(n,i)=k+n+i-2$
注意到每一层都是 加一个$k$,$k$可以在前一层的基础上求出。
有一个细节:若之前求$k$少加了$1$,之后要少减去$1$。
代码:
#include<iostream>
#include<iomanip>
using namespace std;
int n,i,j;
int solve(int n,int x,int y){
if(y==1) return x;
if(y==n) return 3*n-x-1;
if(x==1) return 4*n-y-2;
if(x==n) return n+y-1;
return solve(n-2,x-1,y-1)+4*(n-1);
}
int main() {
ios::sync_with_stdio(false);
cin>>n>>i>>j;
cout<<solve(n,j,i);
return 0;
}
总结
当数据比较小时用模拟法。模拟法要抓住“有规律的变化”,用循环求解。
当数据比较大,或规律明显时用归纳法。归纳法参考等差数列,要利用“初值”,“公差”,“项数”分析公式,也要在最后用加上常数的方法进行调整。另外还要注意数值变化的方向。