引言
高中物理有一个十分经典的问题,即小球碰撞问题。将这个物理问题简化,不考虑能量损耗,且假设每个小球的速度大小与质量都一致,就成为一个经典的算法问题,即给定若干小球的初始位置与速度矢量,计算一段时间后各小球的位置与运动情况。这是一个很经典的问题,也已经有了许多C语言的大佬对此题给出了非常巧妙的算法。笔者作为一个C语言新手,还很难超越前人,创造出更巧妙的算法,但想在此给出一种较为繁复甚至有些笨拙,却能通过模拟这个运动过程最终给出答案的暴力解题法。希望我的解法能作为对这个问题算法一个微不足道的补充,也希望能为和我一样的初学者带来启发。同时,笔者也真诚地期待看到此篇文章的前辈们不吝赐教,对我的算法进行批评指正。
具体解法
本问题的核心在于运动过程中小球的相遇,以及碰撞之后方向的改变,而笔者的代码原本是为另一道与此小球碰撞本质相同的问题书写的。为了方便,我将直接以这道与小球碰撞本质相同的问题为例,对此算法进行解释。
问题描述
长度为 L 的直跑道上有 n 个同学,每个同学只会朝南或北两个方向走,速度都为 1 米每秒 。当两个同学相遇时,两个同学将同时转向(转身时间忽略不计)。现给出每个同学的初始位置和运动方向,计算 T 秒后每个同学的位置。(说明:这里指的位置由同学距离跑道北端起点的距离表示,位于最北端的同学位置是 0 ,位于最南端的同学位置是 L 。)输入
第一行为三个用空格分隔的正整数 L , n , T ( 1 ≤ n ≤ L ≤ e4 , 0 ≤ T ≤ e4 )。之后 n 行,每行一个正整数 x (0 ≤ x ≤ L ),表示同学到跑道最北端起点的距离,和一个字母 c 表示初始朝向。 c 只会为 N 或 S , N 表示朝北, S 表示朝南。 保证每位同学的初始位置不同。
输出
输出 n 行,按输入顺序输出每位同学的位置和朝向,中间用一个空格隔开。对于在第 T 秒之前已经走出 TD 线的同学,输出 Finished 。(说明:朝向用字符表示,N 表示朝北,S 表示朝南,Turning 表示正在转身。)样例输入
10 4 1
1 S
5 S
3 N
10 S
样例输出
2 Turning
6 S
2 Turning
Finished
解题思路
在这个运动过程中,重要因素包括各同学的初始位置和每个同学的速度矢量,其实本题给出数据输入格式时已经将这些这些因素数据化。如果我们把每个同学看做一个对象,将其位置与速度矢量(由于这里速度大小恒为 1 ,因此主要是朝向),赋给这个对象即可。为此,我们可以定义两个数组(字符数组),分别用来记录每位同学的位置和朝向,通过数组(字符数组)的下标实现与每位同学的一一对应。此外,还需定义一个数组表示跑道(相当于数轴)的各个位置坐标。以便判断运动中相遇的情况。接下来,就要通过程序运算模拟每位同学的运动,这可以通过对与每位同学一一对应的位置和朝向数组进行操作来实现。但在考虑运动过程中相遇的情况时,如果选 1 为时间间隔,那么相遇就可能发生在每一时间间隔的中间或者末尾。对于发生在中间的相遇,可以直接互换二者的朝向,位置不变;而当相遇发生在,时间间隔末尾时,二者在这一瞬间会“重叠”。这时,一个表示各跑道位置坐标的数组已经不够,还需要再定义一个相同的数组以储存“重叠”的对象。
在运动结束的瞬间,仍可能发生相遇转身的情况,因此最后还应再对可能的相遇情况进行一次处理。
代码实现
#include<stdio.h>
int Position[10100]; //以数组Position记录每位同学的位置,数组的下标即同学的编号;
int Record[10100],AnotherRecord[10100]; //以数组Record记录跑道上每个位置是否有同学,以跑道为x轴,数组的下标即不同位置的坐标;数组AnotherRecord用于位置重叠时的记录;
int op[10100]; //以数组op标记对应同学所作的操作——继续前进或转身,数组的下标即同学的编号;
char Der[10100]; //定义字符数组Der,记录每位同学的前进方向,数组的下标即同学的编号;
int L,n,T;
void ChangeDirection(int a,int b)
{
char carrier;
carrier=Der[a];
Der[a]=Der[b];
Der[b]=carrier;
}
int main()
{
int i;
int t; //记录跑道上同学的运动时间;
scanf("%d%d%d",&L,&n,&T);
for(i=1; i<=n; i++)
scanf("%d %c",&Position[i],&Der[i]); //录入每位同学的初始位置与运动方向;
for(t=1; t<=T; t++) //t的增加代表时间的演进;
{
for(i=0; i<10010; i++) //初始化数组;
{
Record[i]=-1;
op[i]=0;
AnotherRecord[i]=-1;
}
for(i=1; i<=n; i++)
{
if(Position[i]>=0&&Position[i]<=L)
{
if(Record[Position[i]]==-1)
Record[Position[i]]=i; //数组Record中对应的数字为-1代表该位置为空,可以直接占据这个位置,并将Record中该点对应的值赋为占据这个点的同学的编号;
else
{
ChangeDirection(i,Record[Position[i]]);
AnotherRecord[Position[i]]=i; //若该位置有人的话,则两者交换前进方向,位置坐标不变
}
}
else
{
Position[i]=-1;
Der[i]='F'; //以'F'表示该同学已走出跑道,走出跑道的同学位置统一标记为-1;
}
}
for(i=1; i<=L; i++) //遍历一次每位同学的运动情况,检查本次运动中是否会有相遇的情况;
{
if(Position[i]>0)
{
/*注:这里在判断相遇时,由于部分位置可能存在重叠,因此要检查运动的两端是否存在重叠,以及重叠的两个人各自的相遇情况,即4种情况*/
if(Record[Position[i]-1]!=-1&&
Record[Position[i]]!=-1&&
Der[Record[Position[i]-1]]=='S'&&
Der[Record[Position[i]]]=='N'&&
op[Record[Position[i]-1]]==0&&
op[Record[Position[i]]]==0)
{
ChangeDirection(Record[Position[i]-1],Record[Position[i]]);
op[Record[Position[i]-1]]=1;
op[Record[Position[i]]]=1;
}
else if(AnotherRecord[Position[i]-1]!=-1&&
AnotherRecord[Position[i]]!=-1&&
Der[AnotherRecord[Position[i]-1]]=='S'&&
Der[AnotherRecord[Position[i]]]=='N'&&
op[AnotherRecord[Position[i]-1]]==0&&
op[AnotherRecord[Position[i]]]==0)
{
ChangeDirection(AnotherRecord[Position[i]-1],AnotherRecord[Position[i]]);
op[AnotherRecord[Position[i]-1]]=1;
op[AnotherRecord[Position[i]]]=1;
}
else if(AnotherRecord[Position[i]-1]!=-1&&
Record[Position[i]]!=-1&&
Der[AnotherRecord[Position[i]-1]]=='S'&&
Der[Record[Position[i]]]=='N'&&
op[AnotherRecord[Position[i]-1]]==0&&
op[Record[Position[i]]]==0)
{
ChangeDirection(AnotherRecord[Position[i]-1],Record[Position[i]]);
op[AnotherRecord[Position[i]-1]]=1;
op[Record[Position[i]]]=1;
}
else if(Record[Position[i]-1]!=-1&&
AnotherRecord[Position[i]]!=-1&&
Der[Record[Position[i]-1]]=='S'&&
Der[AnotherRecord[Position[i]]]=='N'&&
op[Record[Position[i]-1]]==0&&
op[AnotherRecord[Position[i]]]==0)
{
ChangeDirection(Record[Position[i]-1],AnotherRecord[Position[i]]);
op[Record[Position[i]-1]]=1;
op[AnotherRecord[Position[i]]]=1;
}
}
}
for(i=1; i<=n; i++)
{
if(Der[i]=='S'&&op[i]==0)
Position[i]=Position[i]+1;
else if(Der[i]=='N'&&op[i]==0)
Position[i]=Position[i]-1;
}
//数组op对应的值为0时表示继续前进,值为1时表示本次转身,不前进;
}
/*最后一秒末可能存在两位同学正好走到同一位置的情况,因此要再判断一次*/
for(i=0; i<10010; i++)
Record[i]=-1;
for(i=1; i<=n; i++)
{
if(Position[i]>=0&&Position[i]<=L)
{
if(Record[Position[i]]==-1)
Record[Position[i]]=i;
else if(Record[Position[i]]!=-1)
{
Der[i]='T';
Der[Record[Position[i]]]='T';
}
}
else
{
Position[i]=-1;
Der[i]='F';
}
}
for(i=1; i<=n; i++)
{
if(Der[i]=='T')
printf("%d Turning\n",Position[i]);
else if(Der[i]=='F')
printf("Finished\n");
else
printf("%d %c\n",Position[i],Der[i]);
}
return 0;
}
总结
将一个过程的影响因素参数化,再通过代码运算模拟这个过程,可以实现对部分问题的暴力破解,这实际上也是将真实世界的问题数据化、程序化的过程,对锻炼理性思维有着一定的积极作用。例题原作者:Inx From BUAA