P11854 [CSP-J2022 山东] 宴会
当时这个题是我考试题,考试的时候感性理解出来的三分做法。
首先咱感性理解一下,当\(x_0\)位于左边无穷远处时,答案是个很大的数。
然后随着\(x_0\)从左向右移动,答案呈递减趋势。当\(x_0\)到达答案那个点时,距离最小。然后\(x_0\)接着向右移动,答案又递增。当\(x0\)位于右侧无穷远处时,答案又会趋近于无穷大。
所以我们猜想,\(ans\)随\(x_0\)变化的函数先递减再递增。当\(x_0\)位于谷底时,\(ans\)就会取最小值。
接下来我们简单证明一下结论。
首先我们设\(i\)到集合地点所需时间关于\(x_0\)的函数为\(f(x_0)\)。显然\(f(x_0)=|x_0-x_i|+t_i\),图像如下:

这是一个折线。对于其他的\(i\),也应该是这样的折线。我们把所有的\(n\)条折线画在同一平面直角坐标系中,大概这样:

而对于一个确定的\(x_0\),聚集时间就是这一坨函数里\(x_0\)对应的因变量的最大值,对应到图像里就是\(x=x_0\)与各个图像交点里最高的点的纵坐标。

至于聚会时间关于\(x_0\)的函数图像,很明显就是对于每个横坐标\(x_0\)都取一下最大值得到的点组成的图像。比如这样。

看起来,对于这种情况来说是个折线。但是如何说明其他情况也是折线呢?
我们把这个过程看作一个函数图像两两合并的过程,也就是对于两个函数做上面的过程。这样事情就会简单很多,因为它们的斜率相同,位置关系无非是包含和相交两种。
如果是包含的话,显然合并后结果就是上面的函数。这种情况下两根折线合并后还是折线。

如果是相交的话,各取两个函数比较高的线,最终还是个折线。

所以折线间两两合并,最终结果还是折线,也就证明了我们可以用三分解决本题。
代码:
P11854
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e5+5;
const int inf=1e16;
const double sta=1e-5;
const double STA=0.01;
int T,n,pos[N],t[N];
inline double f(double x){
double ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,fabs(pos[i]-x)+t[i]);
}
return ans;
}
signed main(){
T=read();
while(T--){
n=read();
int mi=inf,ma=0;
for(int i=1;i<=n;i++){
pos[i]=read();
mi=min(mi,pos[i]);ma=max(ma,pos[i]);
}
for(int i=1;i<=n;i++){
t[i]=read();
}
//实数三分
double l=mi,r=ma;//显然答案不会在这个区间外
while(r-l>sta){
double lmid=l+(r-l)/3,rmid=r-(r-l)/3;
double ans1=f(lmid),ans2=f(rmid);//f(x0):求聚会时间
if(ans1>ans2){
l=lmid;
}
else{
r=rmid;
}
}
//底下是自制的一个判断整数
//具体来说,如果最终结果与它相邻的两个整数的误差不超过允许的范围,那我们就认为它是整数,否则就是小数
double ll=floor(l);
double lll=ceil(l);
if(STA>l-ll){
int ans=ll;
printf("%lld\n",ans);
}
else if(STA>lll-l){
int ans=lll;
printf("%lld\n",ans);
}
else{
printf("%.1lf\n",l);
}
}
return 0;
}

浙公网安备 33010602011771号