应用:在若干一次函数 f i ( x ) = k i x + b i f_i(x)=k_ix+b_i fi(x)=kix+bi 中,求 max i f i ( x ) \max_i f_i(x) maxifi(x)
应用拓展:辅助斜率优化。(不过是 O ( n l o g n ) O(n log n) O(nlogn) ,不推荐)
对于区间 $\left[ l,r \right] $ ,设 i d l , r id_{l,r} idl,r 为在区间中点 x m i d x_{mid} xmid 上的最值线段,称之为“优势线段”。为了方便理解,这里把最值设为最大值。
插入直线步骤如下:
-
区间的 i d id id 为空,则将 i d id id 设为当前直线。否则考虑下一步。
-
若当前直线在 x m i d x_{mid} xmid 上的取值大于优势直线在 x m i d x_{mid} xmid 上的取值,那么交换当前直线和优势直线。
-
考虑到在区间中,优势直线并不一定处处大于当前直线。那么求出两条直线的交点 x 0 x_0 x0,若其在区间中,则向它所在的左/右子区间递归,重复第一步。
实现如下:
struct Line{
double k,b;
double at(int x){return k*x+b;}
double intersect(Line B){return (B.b-b)*1.0/(k-B.k);}
}ll[N];
void upd(int p,int l,int r,int i)
{
if(!id[p])
{
id[p]=i;
return;
}
if(l==r) return;
if(ll[i].at(mid)>ll[id[p]].at(mid)) swap(id[p],i);
if(ll[i].k==ll[id[p]].k) return;//细节
if(ll[id[p]].intersect(ll[i])>mid) upd(p<<1|1,mid+1,r,i);
else upd(p<<1,l,mid,i);
}
查询实现如下:
先上代码
int qr1(int p,int l,int r,int x)
{
if(!id[p]) return -1e18;
if(l==r) return ll[id[p]].at(x);
int ret=ll[id[p]].at(x);
if(x<=mid) ret=max(ret,qr1(p<<1,l,mid,x));
else ret=max(ret,qr1(p<<1|1,mid+1,r,x));
return ret;
}
意思就是对于那些包含 x x x 的区间,逐级向下求优势直线在 x x x 处的取值。感性理解,其实对于区间的一些节点,最底层的区间的优势直线是空的,只需要调用覆盖这它的直线即可,而这些直线被它的父区间记录着。
注意一些细节:
-
求交点时特判斜率是否相同
-
x x x 定义域中有负数时要偏移
-
用两点式求 k k k , b b b 时记得转精度
一些例题: