NOIP2014普及组复赛试题解答
3. 螺旋矩阵
【问题描述】
一个n 行n列的螺旋矩阵可由如下方法生成:
从矩阵的左上角(第1行第1列)出发,初始时向右移动:如果前方是未曾经过的格子,则继续前进,否则右转;重复上述操作直至经过矩阵中所有格子。
根据经过顺序,在格子中依次填入1,2,3,…,n2,便构成了一个螺旋矩阵。
现给出矩阵大小n以及i和j,请你求出该矩阵中第i行第j列的数是多少。
【分析】
这是个蛇形填数问题。
如果采用先枚举二维数组再找对应的元素方法,由于1 ≤n ≤30,000,需要建立一个 30,000× 30,000的二维数组,结果会发生数据溢出且超出运行内存上限(128M)。
我们可以采用类似贪吃蛇的方法,让它在N×N个方格内自外向内逐格移动,控制其向右转的方向,并计算其长度。
解法一
#include<cstdio>
using namespace std;
bool pd(int,int) ;
int i,j;
bool p;
int main()
{
int n,x,y,u,d,l,r,tot=0; // U为上边界,D为下边界,L为左边界,R为右边界;
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
scanf("%d%d%d",&n,&i,&j);
d=n;r=n;u=1;l=1; //各边界赋初值;
x=1;y=0;
p=true;
while((tot<n*n)&&p)
{
while((y<r)&&p){++y;++tot;pd(x,y);}--r;//在上侧边界上向右移动,当上侧一行的结束时,控制其右边界向左缩一列;
while((x<d)&&p){++x;++tot;pd(x,y);}--d;//在右侧边界上向下移动,当右侧一列的结束时,控制其下边界向上缩一行;
while((y>l)&&p){--y;++tot;pd(x,y);}++l;//在下侧边界上向左移动,当下侧一行的结束时,控制其左边界向右缩一列;
while((x>u+1)&&p){--x;++tot;pd(x,y);}++u;//在左侧边界上向上移动,当左侧一列的结束时,控制其上边界向下缩一行;
}
printf("%d\n",tot);
fclose(stdin);fclose(stdout);
return 0;
}
bool pd(int x,int y) //判断是否到达目的地,如果到达则停止枚举;
{
if((x==i)&&(y==j))p=false;
return P;
}
解法二:
在上一个解法中,如果遇到极端情况时,可能需要枚举达900000000次,这显然太慢了些,我们可以根据贪吃移动的特点对程序进行优化。
可以这样考虑,当贪吃蛇到每个行列的转折点时,可以先判断目的地是否在自己的正前方,如果是则只需计算当前位置到目的地的距离加上自身的长度即可;否则就计算到下一个转折点人距离,加上当前自身长度,并到达下一个转折点,同时控制所在行(或列)的边界向内侧移动;
#include<cstdio>
using namespace std;
bool pd(int,int) ;
int main()
{
int n,i,j,x,y,u,d,l,r,tot=1; // U为上边界,D为下边界,L为左边界,R为右边界;
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
scanf("%d%d%d",&n,&i,&j);
d=n;r=n;u=1;l=1; //各边界赋初值;
x=1;y=1;
bool p=true;
while(p)
{
if(p)
if(x==i) //在上侧边界线上向右移动。
如果目的地在正前方,则当前长度加上当前位置到目的地距离,结束循环;
{ tot=tot+j-y; p=false; }
else //否则,当前长度加当前位置至行未的长度,同时到达右边界位置,并控制其上边界向下移动一列;
{ tot=tot+r-y; y=r; ++u; }
if(p)
if(y==j)
{ tot=tot+i-x ; p=false; }
else
{ tot=tot+d-x ; x=d; --r; }
if(p)
if(x==i)
{tot=tot+y-j;p=false;}
else
{tot=tot+y-l;y=l;--d; }
if(p)
if(y==j)
{tot=tot+x-i;p=false;}
else
{tot=tot+x-u;x=u;++l;}
}
printf("%d\n",tot);
fclose(stdin);fclose(stdout);
return 0;
}
解法三:
对解法二,我们还可以进行优化。
考虑到贪吃蛇总是从外围一圈圈地向目的地前进,而每一圈刚好是一个正方形,我们可以先计算外围各圈正方形的周长之和,再让贪吃蛇从目的地所在圈的左上角出发,计算其从出发地到目的地的长度就可以了。
#include<cstdio>
using namespace std;
bool pd(int,int) ;
int main()
{
int n,i,j,x,y,u,d,l,r,k,t,tot=0; // U为上边界,D为下边界,L为左边界,R 为右边界;
bool p=true;
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
scanf("%d%d%d",&n,&i,&j);
x=i<n-i+1?i:n-i+1; //判定目的在哪一圈;
y=j<n-j+1?j:n-j+1;
k=x<y?x:y;
u=k; l=k; d=n-k+1 ; r=n-k+1; //设定各边界;
for( t=1; t<k; ++t)
{ tot=tot+(n-1)*4; n=n-2; } //计算在到达目的地外围所有圈的周长之和;
x=k;y=k;++tot; //进入目的地所在的那一圈,初始化出发地;
if(p)
if(x==i) //向右移动。
如果目的地在正前方,则当前长度加上当前位置到目的地距离,结束循环;
{tot=tot+j-y;p=false;}
else //否则,当前长度加当前位置至行未的长度,到达右边界位置;
{tot=tot+r-y;y=r;}
if(p)
if(y==j)
{tot=tot+i-x;p=false;}
else
{tot=tot+d-x;x=d; }
if(p)
if(x==i)
{tot=tot+y-j;p=false;}
else
{tot=tot+y-l;y=l;}
if(p)
if(y==j)
{tot=tot+x-i;p=false;}
else
{tot=tot+x-u;}
printf("%d\n",tot);
fclose(stdin);fclose(stdout); return 0;
}。