其他分享
首页 > 其他分享> > CF47E题解

CF47E题解

作者:互联网

原题

CF47E Cannon


思路概述

题意分析

给定 \(n\) 个起点坐标 \((0,0)\) 速度大小同为 \(v\) ,抛射角分别为 \(\alpha_1,\alpha_2...\alpha_n\) 的炮弹初速度;再给定 \(m\) 面顶端坐标为 \((x_1,y_1),(x_2,y_2)...(x_m,y_m)\) 的墙壁,求每颗炮弹的最终落点(不考虑滑落和滚动)。

思路分析

一眼物理题,套斜抛运动公式即可。但是 \(1≤n≤10^4,1≤m≤10^5\) 的数据使得对每一颗炮弹枚举所有墙壁的算法不可能在时限内跑完,所以考虑贪心。


算法实现

关于斜抛运动公式

由高中物理必修二第一章的物理知识不难推导出斜抛运动的射程、射高公式。由于本题只要求求解最终落点而非某一时刻炮弹位置,所以我们采用斜抛运动的轨迹方程。

速度的分解不解释,直接套公式即可:

\[v_x=v\cos\alpha,v_y=v\sin\alpha \]

首先考虑一种特殊情况:炮弹没有碰到墙壁或已经越过所有墙壁。这时炮弹的最终坐标即为其最远射程与地面形成的点,即 \((x_{max},0)\) 。由相关物理常识可以得出(重力加速度 \(g\) 根据题目要求取 \(9.8m/s^2\) ,下文同理):

\[x_{max}=v_x\frac{2v_y}{g} \]

对于给定水平位移求竖直高度的问题,回到牛顿力学运动公式本身即可推导:

\[∵x=v_xt,y=v_yt-\frac{1}{2}gt^2 \]

\[∴t=\frac{x}{v_x} \]

\[∴y=\frac{v_yx}{v_x}-\frac{gx^2}{2v_x^2} \]

关于排序和贪心

如前文所述,由于本题数据规模的原因,所以不可能用 \(O(nm)\) 的时间复杂度来对每颗炮弹分别枚举所有墙壁,故考虑排序后的贪心策略。

阅读题目时,不难注意到,所有同炮弹的抛射角都满足 \(\alpha_i∈(0,\frac{\pi}{4}]\) 。又根据物理常识可知,这个范围内抛射物在任意时间点的高度都是随抛射角递增的。所以得出结论:在本题抛射角要求范围内,抛射角越大,更有可能越过更多的墙壁。因此,我们先将给定的速度分解后,按抛射角从小到大排序。

贪心算法和双指针

将墙壁按横坐标排序,原因不再冗述。

由上文所得结论,不难得知:在按抛射角递增的顺序给所有炮弹排序后,以当前炮弹的速度 \(v_i(1≤i≤n)\) 可以越过的墙壁(设其集合为 \(W_i\)),其后抛射角更大的炮弹一定能越过,即 \(W_i⊆W_j(i<j)\) 。

根据上述结论,就可以形成本题的算法流程:首先用变量 \(i\) 枚举炮弹,然后用 \(j(r_{i-1}≤j≤m)\) 枚举墙壁(此处 \(r_i\) 表示第 \(i\) 颗炮弹能越过的最远的墙壁下标),并使用上文提到的轨迹方程计算坐标即可。

一些注意事项

由于在最开始的预处理中需要将炮弹按抛射角大小排序,而本题又采用的是离线查询,所以需要在读入数据时给每颗炮弹编号,计算出坐标后再将结果映射回原下标。

除去上文的贪心算法,本题的数据中还存在少数特例,即炮弹没有碰到任何墙壁或已经越过了所有墙壁。这时则直接返回抛物线与水平面的交点坐标。


AC code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<set>
#include<ctime>
#define RI register int
#define RD register double
#define RN register point
using namespace std;
const int maxn=1e5+10;
const double g=9.8000000000,pi=3.1415926535,eps=1e-10;
typedef struct velocity
{
	double s,agl;
	double x,y,d;
	int ord;
	inline void divide(void)
	{
		x=s*cos(agl);y=s*sin(agl);/*分解速度*/
		d=x*2*y/g;/*最大射程*/ 
		return;
	}
	inline bool operator <(const velocity &x)const
	{
		return x.agl>agl;
	}
};
typedef struct wall
{
	double x,y;
	inline bool operator <(const wall &X)const
	{
		return X.x>x;
	}
};
velocity v[maxn];
wall w[maxn],ans[maxn];/*由于需要对速度按抛射角排序后进行离线查询 所以此处用结构体数组存储答案*/
int n,m;
double vel;/*速度大小*/
inline double calc(velocity a,double x);/*通过斜抛运动轨迹方程计算高度*/
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	cin >> n >> vel;
	for(RI i=1;i<=n;++i)
	{
		v[i].s=vel;cin >> v[i].agl;
		v[i].divide();v[i].ord=i;
	}
	sort(v+1,v+n+1);
	cin >> m;
	for(RI i=1;i<=m;++i) cin >> w[i].x >> w[i].y;
	sort(w+1,w+m+1);
	for(RI pv=1,pw=1/*pv为速度指针 pw为墙壁指针*/;pv<=n;++pv)
		if(w[pw].x>v[pv].d) ans[v[pv].ord]=(wall){v[pv].d,0};/*墙壁横坐标已经大于最大射程*/
		else
		{
			while(w[pw].y<calc(v[pv],w[pw].x) && pw<=m) ++pw;/*当前墙壁无法挡住炮弹 墙壁指针后移*/
			if(pw<=m) ans[v[pv].ord]=(wall){w[pw].x,calc(v[pv],w[pw].x)};/*炮弹落在墙壁上*/
			else ans[v[pv].ord]=(wall){v[pv].d,0};/*炮弹越过所有墙壁落在空地上*/
		}
	for(RI i=1;i<=n;++i) printf("%.9lf %.9lf\n",ans[i].x,ans[i].y);	
	return 0;
}
inline double calc(velocity a,double x)
{
	return a.y*(x/a.x)-0.5*g*(x/a.x)*(x/a.x);/*斜抛轨迹方程 详见题解*/
}

标签:frac,墙壁,题解,double,炮弹,CF47E,抛射,include
来源: https://www.cnblogs.com/frkblog/p/16302207.html