维护一次函数
以 模板题 为例。
使用线段树维护线段,每个节点维护的都是完全覆盖这个区间的线段。
考虑当前节点已经有线段 \(f\),现在加入线段 \(g\)。
暴力想法是暴力递归每个子区间,把更优的保留,注意到 \(f,g\) 最多一个交点,因此也最多一侧的子区间需要暴力递归。
具体流程如下:
先比较 \(mid\) 处点值,钦定 \(f\) 为 \(mid\) 处点值不劣的线段。
-
若 \(l\) 处点值 \(g\) 更优,说明交点在左区间,那么右区间更优的仍为 \(f\),左区间递归处理。
-
若 \(r\) 处点值 \(g\) 更优,说明交点在右区间,那么左区间更优的仍未 \(g\),右区间递归处理。
-
特别地,若 \(l\) 或 \(r\) 处点值相等,且新增加的线段在 \(mid\) 处点值更优,那么就递归处理对应的区间。
-
处理之后保留在当前节点的线段应当为 \(f\),即 \(mid\) 处点值不劣的线段。
这样类似于标记永久化,查询时递归每个区间,总复杂度 \(O(n\log^2 n)\)。(如果添加直线就是 \(O(n\log n)\)。)
点击查看代码
int n,X=39989,Y=1000000000;
struct Line{
db k,b;
Line()=default;
Line(db k_,db b_):k(k_),b(b_){}
}L[maxn];
int tot;
inline db get_y(int id,int x){
if(!id) return 0;
return L[id].k*x+L[id].b;
}
inline int check(int id1,int id2,int x){
db y1=get_y(id1,x),y2=get_y(id2,x);
if(y1-y2>eps) return 1;
else if(y2-y1>eps) return -1;
else return 0;
}
inline pdi max(pdi A,pdi B){
if(A.fir-B.fir>eps) return A;
else if(B.fir-A.fir>eps) return B;
else{
if(A.sec<B.sec) return A;
else return B;
}
}
struct SegmentTree{
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
int tag[maxn<<2];
void update(int rt,int l,int r,int id){
if(!tag[rt]) return tag[rt]=id,void();
if(check(id,tag[rt],mid)==1) swap(tag[rt],id);
int chkl=check(id,tag[rt],l),chkr=check(id,tag[rt],r);
if(chkl==1||(!chkl&&id<tag[rt])) update(lson,id);
if(chkr==1||(!chkr&&id<tag[rt])) update(rson,id);
}
void insert(int rt,int l,int r,int pl,int pr,int id){
if(pl<=l&&r<=pr){
update(rt,l,r,id);
return;
}
if(pl<=mid) insert(lson,pl,pr,id);
if(pr>mid) insert(rson,pl,pr,id);
}
pdi query(int rt,int l,int r,int x){
db y=get_y(tag[rt],x);
pdi res=make_pair(y,tag[rt]);
if(l==r) return res;
if(x<=mid) res=max(res,query(lson,x));
else res=max(res,query(rson,x));
return res;
}
#undef mid
#undef lson
#undef rson
}S;
int lastans;
int main(){
n=read();
while(n--){
int opt=read();
if(opt==1){
int x0=read(),y0=read(),x1=read(),y1=read();
x0=(x0+lastans-1)%X+1,y0=(y0+lastans-1)%Y+1,x1=(x1+lastans-1)%X+1,y1=(y1+lastans-1)%Y+1;
if(x0>x1) swap(x0,x1),swap(y0,y1);
if(x0==x1) L[++tot]=Line(0,max(y0,y1));
else{
db k=1.0*(y1-y0)/(x1-x0),b=y0-k*x0;
L[++tot]=Line(k,b);
}
S.insert(1,1,X,x0,x1,tot);
}
else{
int x=read();
x=(x+lastans-1)%X+1;
lastans=S.query(1,1,X,x).sec;
printf("%d\n",lastans);
}
}
return 0;
}
斜率优化 DP
斜率优化 DP 中,通常写成 \(y=kx+b\) 的形式,其中 \(y,x\) 均与决策点 \(j\) 有关,通常是使用数据结构维护凸包。
在 \(x\) 具有单调性的前提下,\(k\) 具有单调性的情况,可以单调队列维护;反之可以单调栈维护并二分。
当 \(x\) 不具有单调性时,就需要用到李超线段树。
直接抛弃前面对凸包的依赖,我们所要找的实际上过 \((x_j,y_j)\) 的且斜率为 \(k\) 直线中,截距满足最值要求的一个。直接移项成 \(b=-x_j\times x+y_j\),这样就是把 \(x=k\) 代入求最值了,可以李超线段树维护。
因此李超线段树可以解决没有任何单调性的斜率优化。
例题
Luogu-P4254 JSOI2008 Blue Mary 开公司
维护一次函数模板。
Luogu-P2497 SDOI2012 基站建设
运用几何知识可以得到:\(\sqrt{r_i'}=\dfrac{x_i-x_j}{2\sqrt{r_j}}\)。
转移方程:
\[f_i=v_i+\min_{j=1}^{i-1} f_j+\dfrac{x_i-x_j}{2\sqrt{r_j}} \]移项后意识到 \(X_j=-\dfrac{1}{2\sqrt{r_j}},Y_j=f_j-\dfrac{X_j}{2\sqrt{r_j}}\),李超线段树维护。(离散化或动态开点均可)
参考资料
-
OI Wiki