湖之精灵的游戏
一个二维平面上有\(n\)个点,第\(i\)个点的坐标是\((x_i,y_i)\),你要和湖之精灵玩游戏。
每次精灵会告诉你一个坐标\((x,y)\),连一条经过\((0,0),(x,y)\)的直线,这会把平面分成两个部分\(A,B\)。你要选择一个区间\([l,r]\),那么计算的时候只会考虑编号在\([l,r]\)之内的点。
一个半平面的价值是这个半平面中,编号在\([l,r]\)范围内的点与\((0,0),(x,y)\)组成三角形的面积之和。
你要选择一个半平面,精灵会选择另一个半平面,你需要最大化你得到的价值减去精灵得到的价值的结果。
有\(m\)轮游戏,对于每轮游戏输出这个最大值乘以\(2\)的结果。
数据范围:\(n,m\leq 10^6,1\leq x,y\leq 1000\)。
题解
题面的意思就是每次询问给定\((x,y)\),让你自选区间\([l,r]\),求
\[\max_{l,r}\{|\sum_{i=l}^r(xy_i-yx_i)|\} \]
利用前缀和思想观察绝对值式子
\[|x\text{sy}_r-y\text{sx}_r-(x\text{sy}_{l-1}-y\text{sx}_{l-1})| \]
为了让这个绝对值最大化,我们只需要找到最大的\(x\text{sy}_i-y\text{sx}_i\)和最小的\(x\text{sy}_j-y\text{sx}_j\)。并且绝对值符号使得我们不必拘泥于大的减小的。
怎么找最大值和最小值呢?老生常谈的斜率优化。
\[f=x\text{sy}_i-y\text{sx}_i \]
\[sy_i=\frac{y}{x}\text{sx}_i+\frac{f}{x} \]
最大值做个上凸包,最小值做个下凸包。
这个题也没有强制在线,直接双指针即可。
时间复杂度\(O(n\log n)\)。
struct point {int64 x,y;};
IN point operator-(CO point&a,CO point&b){
return {a.x-b.x,a.y-b.y};
}
IN int64 cross(CO point&a,CO point&b){
return a.x*b.y-a.y*b.x;
}
CO int N=1e6+10;
point p[N],up[N],dn[N];
struct node {point p;int i;} q[N];
int64 ans[N];
int main(){
freopen("lake.in","r",stdin),freopen("lake.out","w",stdout);
int n=read();
for(int i=1;i<=n;++i)
p[i].x=p[i-1].x+read(),p[i].y=p[i-1].y+read();
int s=1;
up[1]=p[0];
for(int i=1;i<=n;++i){
for(;s>=2 and cross(p[i]-up[s-1],up[s]-up[s-1])<=0;--s);
up[++s]=p[i];
}
int t=1;
dn[1]=p[0];
for(int i=1;i<=n;++i){
for(;t>=2 and cross(p[i]-dn[t-1],dn[t]-dn[t-1])>=0;--t);
dn[++t]=p[i];
}
int m=read();
for(int i=1;i<=m;++i)
read(q[i].p.x),read(q[i].p.y),q[i].i=i;
sort(q+1,q+m+1,[&](CO node&a,CO node&b)->bool{
return cross(a.p,b.p)<0;
});
for(int i=1,j=1;i<=m;++i){
for(;j+1<=s and cross(q[i].p,up[j+1]-up[j])>0;++j);
ans[q[i].i]+=cross(q[i].p,up[j]);
}
sort(q+1,q+m+1,[&](CO node&a,CO node&b)->bool{
return cross(a.p,b.p)>0;
});
for(int i=1,j=1;i<=m;++i){
for(;j+1<=t and cross(q[i].p,dn[j+1]-dn[j])<0;++j);
ans[q[i].i]-=cross(q[i].p,dn[j]);
}
for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
return 0;
}