问题描述
试题编号: 201803-2 试题名称: 碰撞的小球 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述
数轴上有一条长度为L(L为偶数)的线段,左端点在原点,右端点在坐标L处。有n个不计体积的小球在线段上,开始时所有的小球都处在偶数坐标上,速度方向向右,速度大小为1单位长度每秒。
当小球到达线段的端点(左端点或右端点)的时候,会立即向相反的方向移动,速度大小仍然为原来大小。
当两个小球撞到一起的时候,两个小球会分别向与自己原来移动的方向相反的方向,以原来的速度大小继续移动。
现在,告诉你线段的长度L,小球数量n,以及n个小球的初始位置,请你计算t秒之后,各个小球的位置。 提示
因为所有小球的初始位置都为偶数,而且线段的长度为偶数,可以证明,不会有三个小球同时相撞,小球到达线段端点以及小球之间的碰撞时刻均为整数。
同时也可以证明两个小球发生碰撞的位置一定是整数(但不一定是偶数)。 输入格式 输入的第一行包含三个整数n, L,
t,用空格分隔,分别表示小球的个数、线段长度和你需要计算t秒之后小球的位置。 第二行包含n个整数a1, a2, …,
an,用空格分隔,表示初始时刻n个小球的位置。 输出格式
输出一行包含n个整数,用空格分隔,第i个整数代表初始时刻位于ai的小球,在t秒之后的位置。 样例输入 3 10 5 4 6 8 样例输出
7 9 9 样例说明 初始时,三个小球的位置分别为4, 6, 8。一秒后,三个小球的位置分别为5, 7, 9。
两秒后,第三个小球碰到墙壁,速度反向,三个小球位置分别为6, 8, 10。
三秒后,第二个小球与第三个小球在位置9发生碰撞,速度反向(注意碰撞位置不一定为偶数),三个小球位置分别为7, 9, 9。
四秒后,第一个小球与第二个小球在位置8发生碰撞,速度反向,第三个小球碰到墙壁,速度反向,三个小球位置分别为8, 8, 10。
五秒后,三个小球的位置分别为7, 9, 9。
样例输入 10 22 30 14 12 16 6 10 2 8 20 18 4 样例输出 6 6 8 2 4 0 4 12 10 2
数据规模和约定 对于所有评测用例,1 ≤ n ≤ 100,1 ≤ t ≤ 100,2 ≤ L ≤ 1000,0 < ai <
L。L为偶数。 保证所有小球的初始位置互不相同且均为偶数。
上张图看看 非暴力 的效率
个人感觉还是很满意的
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct Ball {
int no;
int position;
int direction;
};
bool cop(Ball a, Ball b) {
return a.position < b.position;
}
bool copNo(Ball a, Ball b) {
return a.no < b.no;
}
int main() {
int n = 0, l = 0, t = 0;
cin >> n >> l >> t;
vector<Ball> Balls(n);
for (int i = 0; i <n; i++)
{
Balls[i].no = i;
Balls[i].direction = 1;
cin>>Balls[i].position;
}
sort(Balls.begin(), Balls.end(), cop);
//初始化 n个球 direction代表速度 +-为方向 数值为大小
int PastTime = 0;
while (true) {
//按位置排下序
int MinT = l;
if (Balls[n-1].direction == 1 ) {
MinT = l-Balls[n-1].position;
}//与左端碰撞
if (Balls[0].direction == -1 && Balls[0].position < MinT) {
MinT = Balls[0].position;
}//与右端碰撞
for (int i = 0; i < n-1 ; i++) {
if(Balls[i].direction==1&&Balls[i+1].direction==-1)
if (MinT > (Balls[i + 1].position - Balls[i].position)/2) {
MinT =(Balls[i + 1].position - Balls[i].position)/ 2;
}
}
//相向碰撞
// 找出 在 所有 有碰撞可能的 两对小 球 中的最小时间
PastTime += MinT;
if (PastTime > t) {
for (int i = 0; i < n; i++) {
Balls[i].position += Balls[i].direction*(t - PastTime+MinT);
}
break;
}
//如果下一次的碰撞时间超过了T 就结束while
int i = 0;
if (-1== Balls[0].direction) {
Balls[0].position += Balls[0].direction*MinT;
if(Balls[0].position==0)
Balls[0].direction *= -1;
i = 1;
}//是否为位置最小的球与左端碰撞
while (i < n - 1) {
if (Balls[i].direction == 1 && Balls[i + 1].direction == -1) {
if ((Balls[i].position +=MinT*Balls[i].direction)
== (Balls[i + 1].position +=MinT*Balls[i+1].direction))
{
Balls[i].direction *= -1;
Balls[i + 1].direction *= -1;
}
i = i + 2;
}
else{
Balls[i].position += MinT*Balls[i].direction;
i++;
}
}//判断在1到n-1里有没有发生碰撞的 并改变碰撞的方向
if (i == n - 1) {
Balls[i].position += MinT * Balls[i].direction;
if (Balls[i].position ==l)
Balls[i].direction *= -1;
}//看最后一个 如果 i==n-1即n-1的方向为+ 所以判断是否会碰上
}
sort(Balls.begin(), Balls.end(), copNo);
for (int i = 0; i < n; i++) {
cout << Balls[i].position << " ";
}
}
比暴力做稍微复杂了一些 但是时间上肯定少了很多
思路是这样的
只有当两个相向的小球会碰上 而且 可以算出需要多少时间会碰上 这个时间用距离除以速度
就可以算出来 当当前时间加上这个算出的碰撞的时间即下一次碰撞的时间 大于 题目要求的时间就结束循环
结构体
struct Ball {
int no;
int postion;
int direction;
};
no 为 球的序号
position 为 小球的位置
direction 为 小球的速度 正负代表方向
计算距离发生下一次碰撞还有多少时间
即在所有可能发生碰撞的时间里最小的那个时间
总共有三种可能情况
左端 右端 中间相向
int MinT = l;
if (Balls[n-1].direction == 1 ) {
MinT = l-Balls[n-1].position;
}//与左端碰撞
if (Balls[0].direction == -1 && Balls[0].position < MinT) {
MinT = Balls[0].position;
}//与右端碰撞
for (int i = 0; i < n-1 ; i++) {
if(Balls[i].direction==1&&Balls[i+1].direction==-1)
if (MinT > (Balls[i + 1].position - Balls[i].position)/2) {
MinT =(Balls[i + 1].position - Balls[i].position)/ 2;
}
}
//相向碰撞
// 找出 在 所有 有碰撞可能的 两对小 球 中的最小时间
计算是否有足够的时间发生下次碰撞 即 在游戏结束前下个碰撞会不会发生
PastTime += MinT;
if (PastTime > t) {
for (int i = 0; i < n; i++) {
Balls[i].position += Balls[i].direction*(t - PastTime+MinT);
}
break;
}
因为不会发生碰撞所以遍历一遍直接算position就好
碰撞检测 即 在下一个碰撞发生时 是否还有多个碰撞发生
这个部分是关键所在 累死累死
一开始是想对最左端和最右端和中间分别检测是否会发生碰撞但是问题是
如果最左端最右端没发生碰撞 那么如何处理postion 与direction的关系
就是说在最左端和最右端同时要考虑两种碰撞的情况
1 和 边界碰撞
2 和 相邻的小球相向碰撞
如果把最左端和最右端一起考虑 就是在开头或者结尾处一起处理的话
总有奇奇怪怪的问题出现
原因在于最左右端同时 是 中间的组成部分
或者说是 最左端和次左端 以及边界 绞合在一块
于是我想那就分离成 最左边两个和中间 以及 最右端两个
但是问题又来了
如果只有三个球如何划分 中间的那个的position会计算两次
四个即以上就没有问题
那又该如何我想
1 每个球加一个标记位来记录更改与否
但是 额外空间比较多
2 把三个当成特殊情况
但是这样的话还得考虑2个的情况 一个情况 放弃 太麻烦了
所以 只有从结构上改进
通过对while循环里的i的数值的动态来简化问题
即 while循环是从i开始的
通过对左边界的判断来决定中间开始的位置为 0 or 1
同时 通过对中间的相向的判断来决定最后是否对右边界进行碰撞判断
意思是不在每个元素上设置标识符 而是顺序的过程中 把两个边界的情况当成标识符
int i = 0;
if (-1== Balls[0].direction) {
Balls[0].position += Balls[0].direction*MinT;
if(Balls[0].position==0)
Balls[0].direction *= -1;
i = 1;
}//是否为位置最小的球与左端碰撞
while (i < n - 1) {
if (Balls[i].direction == 1 && Balls[i + 1].direction == -1) {
if ((Balls[i].position +=MinT*Balls[i].direction)
== (Balls[i + 1].position +=MinT*Balls[i+1].direction)) {
Balls[i].direction *= -1;
Balls[i + 1].direction *= -1;
}
i = i + 2;
}
else{
Balls[i].position += MinT*Balls[i].direction;
i++;
}
}//判断在1到n-1里有没有发生碰撞的 并改变碰撞的方向
if (i == n - 1) {
Balls[i].position += MinT * Balls[i].direction;
if (Balls[i].position ==l)
Balls[i].direction *= -1;
}//看最后一个 如果 i==n-1即n-1的方向为+ 所以判断是否会碰上
中间判断时注意一下一次越两个还是一个
最后按no排一下序输出就好
sort(Balls.begin(), Balls.end(), copNo);
for (int i = 0; i < n; i++) {
cout << Balls[i].position << " ";
}
啊 啊啊啊啊啊 啊就是这样子 还是暴力简单 》》》》