湖之精灵的游戏
一个二维平面上有(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,mleq 10^6,1leq x,yleq 1000)。
题解
题面的意思就是每次询问给定((x,y)),让你自选区间([l,r]),求
[max_{l,r}{|sum_{i=l}^r(xy_i-yx_i)|}
]
利用前缀和思想观察绝对值式子
[|x ext{sy}_r-y ext{sx}_r-(x ext{sy}_{l-1}-y ext{sx}_{l-1})|
]
为了让这个绝对值最大化,我们只需要找到最大的(x ext{sy}_i-y ext{sx}_i)和最小的(x ext{sy}_j-y ext{sx}_j)。并且绝对值符号使得我们不必拘泥于大的减小的。
怎么找最大值和最小值呢?老生常谈的斜率优化。
[f=x ext{sy}_i-y ext{sx}_i
]
[sy_i=frac{y}{x} ext{sx}_i+frac{f}{x}
]
最大值做个上凸包,最小值做个下凸包。
这个题也没有强制在线,直接双指针即可。
时间复杂度(O(nlog 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<int>();
for(int i=1;i<=n;++i)
p[i].x=p[i-1].x+read<int>(),p[i].y=p[i-1].y+read<int>();
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<int>();
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
",ans[i]);
return 0;
}