1. 题意
DVD 机在视频输出时,为了保护电视显像管,在待机状态会显示 “屏保动画”,DVD Logo 在屏幕内来回运动,碰到边缘会反弹。
请根据如下要求,实现屏保 Logo 坐标的计算算法:
屏幕与坐标系:屏幕是 800×600 像素的矩形,左上角为坐标原点,横边向右为 X 轴,竖边向下为 Y 轴。
Logo 初始状态:Logo 是 50×25 像素的矩形,初始左上角坐标为 (x, y),在 X、Y 方向均以 1 像素 / 秒的速度运动。
边缘反弹:碰到屏幕四个边缘(左、右、上、下)时,发生镜面反弹(以 45° 碰撞并反弹)。
角落碰撞:碰到屏幕四个角时,因两个边缘同时反弹,Logo 会原路返回。
2. 题解
由于小球在两个方向上的运动是独立的,因此我们可以分方向进行计算。
总体代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;
static constexpr int WIND_W = 800;
static constexpr int WIND_H = 600;
static constexpr int REC_W = 50;
static constexpr int REC_H = 25;
static constexpr int MXX = WIND_W - REC_W;
static constexpr int MXY = WIND_H - REC_H;
int vx = 1;
int vy = 1;
int dx = 1;
int dy = 1;
int x;
int y;
int t;
void solve1()
{
int cx = x;
int cy = y;
for (int i = 0;i < t; ++i) {
int nx_l = cx + dx * vx;
int nx_r = cx + REC_W - 1 + dx * vx;
// nx_r = nx_l + REC_W - 1
// nx_r <= WIND_W - 1 <==> nx_l < WIND_W - REC_W
if ( nx_l < 0 || nx_r >= WIND_W) {
dx *= -1;
}
// ny_b = ny_u + REC_H - 1
// ny_b <= WIND_H - 1 <==> ny_u <= WIND_H - REC_H
int ny_u = cy + dy * vy;
int ny_b = cy + REC_H - 1 + dy * vy;
if ( ny_u < 0 || ny_b >= WIND_H) {
dy *= -1;
}
cx += dx * vx;
cy += dy * vy;
}
cout << cx << " " << cy << endl;
}
void solve1_1()
{
int cx = x;
int cy = y;
for (int i = 0; i < t; ++i) {
int nx = cx + dx ;
int ny = cy + dy ;
if ( nx < 0 || nx > MXX ) {
dx *= -1;
}
if ( ny < 0 || ny > MXY ) {
dy *= -1;
}
cx += dx;
cy += dy;
}
cout << cx << " " << cy << endl;
}
void solve2()
{
vector<int> posx;
vector<int> posy;
int Tx = 2 * (WIND_W - REC_W);
int Ty = 2 * (WIND_H - REC_H);
int cx = x;
int cy = y;
int inc = 1;
for (int i = 0; i < Tx; ++i) {
posx.push_back(cx);
if (cx == MXX) {
inc = -1;
}
else if ( cx == 0) {
inc = 1;
}
cx += inc;
}
inc = 1;
for (int i = 0; i < Ty; ++i) {
posy.push_back( cy );
if ( cy == MXY) {
inc = -1;
}
else if ( cy == 0) {
inc = 1;
}
cy += inc;
}
cout << posx[ t % Tx ] << " " << posy[ t % Ty ] << endl;
}
void solve3()
{
auto getPos = [](int start, int t, int MX, int T)->int {
t %= T;
if (t <= MX - start) {
return start + t;
}
else {
return MX - (t - (MX - start));
}
};
cout << getPos( x, t, MXX, 2 * MXX) << " " << getPos( y, t, MXY, 2 * MXY) << endl;
}
int main()
{
cin >> x >> y >> t;
solve1_1();
return 0;
}
2.1 直接模拟
根据移动的方向来计算下一秒的位置,由于矩形logo是矩形的,因此我们需要判断下一秒四个矩形边界是否超出了屏幕的范围。
static constexpr int WIND_W = 800;
static constexpr int WIND_H = 600;
static constexpr int REC_W = 50;
static constexpr int REC_H = 25;
static constexpr int MXX = WIND_W - REC_W;
static constexpr int MXY = WIND_H - REC_H;
int vx = 1;
int vy = 1;
int dx = 1;
int dy = 1;
int x;
int y;
int t;
void solve1()
{
int cx = x;
int cy = y;
for (int i = 0;i < t; ++i) {
int nx_l = cx + dx * vx;
int nx_r = cx + REC_W - 1 + dx * vx;
// nx_r = nx_l + REC_W - 1
// nx_r <= WIND_W - 1 <==> nx_l < WIND_W - REC_W
if ( nx_l < 0 || nx_r >= WIND_W) {
dx *= -1;
}
// ny_b = ny_u + REC_H - 1
// ny_b <= WIND_H - 1 <==> ny_u <= WIND_H - REC_H
int ny_u = cy + dy * vy;
int ny_b = cy + REC_H - 1 + dy * vy;
if ( ny_u < 0 || ny_b >= WIND_H) {
dy *= -1;
}
cx += dx * vx;
cy += dy * vy;
}
cout << cx << " " << cy << endl;
}
事实上我们可以根据左边界和logo的宽度来计算右边界,同理可以根据上边界的位置和高度计算logo下边界的位置。因此我们可以稍微简化一下代码。
xl+logo_w−1=xrxr≤wind_w−1xl≤wind_w−logo_wyu+logo_h−1=ybyb≤wind_h−1yu≤wind_h−logo_h
x_l+logo\_w-1 =x_r\\
x_r \le wind\_w-1\\
x_l \le wind\_w-logo\_w\\
y_u+logo\_h -1=y_b\\
y_b\le wind\_h-1\\
y_u \le wind\_h-logo\_h
xl+logo_w−1=xrxr≤wind_w−1xl≤wind_w−logo_wyu+logo_h−1=ybyb≤wind_h−1yu≤wind_h−logo_h
我们令MXX=wind_w−logo_wMXX=wind\_w-logo\_wMXX=wind_w−logo_w, MXY=wind_h−logo_hMXY=wind\_h-logo\_hMXY=wind_h−logo_h
void solve1_1()
{
int cx = x;
int cy = y;
for (int i = 0; i < t; ++i) {
int nx = cx + dx ;
int ny = cy + dy ;
if ( nx < 0 || nx > MXX ) {
dx *= -1;
}
if ( ny < 0 || ny > MXY ) {
dy *= -1;
}
cx += dx;
cy += dy;
}
cout << cx << " " << cy << endl;
}
时间复杂度O(t)O(t)O(t)
空间复杂度O(1)O(1)O(1)
2.2 预处理周期
我们其实可以观察到,小球在两个方向的运动都是周期性运动的。
因此我们可以预处理出一个周期的所有位置可能,最后再来取答案就可以了。
那么周期是多少呢?
不难发现小球的左上位置实际上是从
0→MX→0
0\rightarrow MX \rightarrow0
0→MX→0
这样循环往复的,因此周期是2MX2MX2MX
void solve2()
{
vector<int> posx;
vector<int> posy;
int Tx = 2 * (WIND_W - REC_W);
int Ty = 2 * (WIND_H - REC_H);
int cx = x;
int cy = y;
int inc = 1;
for (int i = 0; i < Tx; ++i) {
posx.push_back(cx);
if (cx == MXX) {
inc = -1;
}
else if ( cx == 0) {
inc = 1;
}
cx += inc;
}
inc = 1;
for (int i = 0; i < Ty; ++i) {
posy.push_back( cy );
if ( cy == MXY) {
inc = -1;
}
else if ( cy == 0) {
inc = 1;
}
cy += inc;
}
cout << posx[ t % Tx ] << " " << posy[ t % Ty ] << endl;
}
时间复杂度O(1)O(1)O(1)
空间复杂度O(Tx+Ty)O(Tx+Ty)O(Tx+Ty)
2.3 直接计算
事实上知道周期后,我们可以直接计算,因为初始的速度一定是增,我们只需要看时间对周期取余后,是否能到达最大的位置。能到达就再处理下往回走。
void solve3()
{
auto getPos = [](int start, int t, int MX, int T)->int {
t %= T;
if (t <= MX - start) {
return start + t;
}
else {
return MX - (t - (MX - start));
}
};
cout << getPos( x, t, MXX, 2 * MXX) << " " << getPos( y, t, MXY, 2 * MXY) << endl;
}
时间复杂度O(1)O(1)O(1)
空间复杂度O(1)O(1)O(1)