李超线段树
李超线段树
用途:给定 $\mathrm{n}$ 个 $\mathrm{y}=\mathrm{kx}+\mathrm{b}$ 形式的线段,问 $\mathrm{x}=\mathrm{x[0]}$ 与哪条线段交点的纵坐标值最大.
对于李超树的每一个区间,维护这条区间的 “优势线段”,即区间 $\mathrm{mid}$ 位置上纵坐标最大的线段.
当在区间中插入一条线段时,按照下列步骤:
1. 若新插入线段中点位置大于当前线段,则该区间的优势线段更新为新线段,并将当前线段向下递归更新.
2. 若将递归更新的线段左/右端点的值小于优势线段,显然会完全被覆盖掉,就没有必要继续递归了.
3. 若满足递归条件,则递归继续更新.
插入线段时间复杂度为 $O(n \log ^2 n)$,查询复杂度为 $O(n \log n)$.
Segment
来源:洛谷P4097 [HEOI2013]Segment
模板题,注意线段的 $\mathrm{x[0]}=\mathrm{x[1]}$ 时将斜率取为 $0$ 即可.
#include <cstdio> #include <cstring> #include <cmath> #include <vector> #include <set> #include <algorithm> #define M 50003 #define N 100009 #define ll long long #define pb push_back #define ls (now<<1) #define rs (now<<1|1) #define setIO(s) freopen(s".in","r",stdin) using namespace std; const int mod=39989; const double eps=1e-8; int tree[N<<2]; struct seg { double k,b; seg(double i=0.0,double j=0.0) { k=i,b=j;} }s[N]; double calc(seg o, int x) { return o.k*x+o.b; } void modify(int l,int r,int now,int L,int R,int x) { int mid=(l+r)>>1; if(l>=L&&r<=R) { if(calc(s[x],mid)-calc(s[tree[now]],mid)>eps) swap(x, tree[now]); if(calc(s[x],l)-calc(s[tree[now]],l)>eps) modify(l,mid,ls,L,R,x); if(calc(s[x],r)-calc(s[tree[now]],r)>eps) modify(mid+1,r,rs,L,R,x); return ; } if(L<=mid) modify(l,mid,ls,L,R,x); if(R>mid) modify(mid+1,r,rs,L,R,x); } void upd(int &x,int o,int p) { double yx=s[x].k*p+s[x].b; double yo=s[o].k*p+s[o].b; if(yo-yx>=eps||(abs(yo-yx)<=eps&&o<x)) x=o; } int query(int l,int r,int now,int p) { if(l==r) { return tree[now]; } int mid=(l+r)>>1,id=tree[now]; if(p<=mid) upd(id, query(l,mid,ls,p), p); else upd(id, query(mid+1,r,rs,p), p); return id; } int main() { // setIO("input"); // freopen("input.out","w",stdout); int Q,lastans=0,n=0; scanf("%d",&Q); while(Q--) { int op; scanf("%d",&op); if(op==0) { int k,x; scanf("%d",&k); k=(k+lastans-1)%mod+1; printf("%d\n",lastans=query(1,M,1,k)); } else { int x[2],y[2]; scanf("%d%d%d%d",&x[0],&y[0],&x[1],&y[1]); x[0]=(x[0]+lastans-1)%mod+1; y[0]=(y[0]+lastans-1)%1000000000+1; x[1]=(x[1]+lastans-1)%mod+1; y[1]=(y[1]+lastans-1)%1000000000+1; if(x[0]==x[1]) { s[++n].k=0,s[n].b=max(y[0], y[1]); } else { s[++n].k=(double)1.0*(y[1]-y[0])/(x[1]-x[0]); s[n].b=y[0]-1.0*s[n].k*x[0]; } if(x[0]>x[1]) swap(x[0],x[1]); modify(1,M,1,x[0],x[1],n); } } return 0; }
游戏
来源:洛谷P4069 [SDOI2016]游戏
树链剖分套李超线段树,时间复杂度为 $O(n \log ^3 n)$,不过树剖和李超树的常数都很小.
将这个路径加数写成对于点 $\mathrm{i}$ 只关于 $\mathrm{dis[i]}$ 的式子后发现是 $\mathrm{ax+b}$ 的形式.
本题在模板的基础上再维护一个区间极小值就行,然后路径修改时对于 $\mathrm{lca}$ 两侧分类讨论一下.
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #define N 100008 #define ls now<<1 #define rs now<<1|1 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; const ll inf=123456789123456789ll; struct seg { ll k,b; seg() { k=b=0; } }s[N]; ll mi[N<<2],pl[N<<2],pr[N<<2],dis[N]; int tree[N<<2],idx[N<<2],dep[N],n,m,edges,cn; int hd[N],to[N<<1],nex[N<<1],val[N<<1],fa[N],size[N],son[N],top[N],dfn[N],scc; ll calc(seg o, ll pos) { return o.k*pos+o.b; } ll get(int now) { if(!tree[now]) return inf; else { return min(calc(s[tree[now]], pl[now]), calc(s[tree[now]], pr[now])); } } void pushup(int now) { mi[now]=min(get(now), min(mi[ls], mi[rs])); } void build(int l,int r,int now) { mi[now]=inf; pl[now]=dis[idx[l]]; pr[now]=dis[idx[r]]; if(l==r) return ; int mid=(l+r)>>1; build(l,mid,ls); build(mid+1,r,rs); } ll query(int l,int r,int now,int L,int R) { if(l>=L&&r<=R) { return mi[now]; } ll re=inf; int mid=(l+r)>>1; if(tree[now]) { re=min(re, calc(s[tree[now]], dis[idx[max(l,L)]])); re=min(re, calc(s[tree[now]], dis[idx[min(r,R)]])); } if(L<=mid) re=min(re, query(l,mid,ls,L,R)); if(R>mid) re=min(re, query(mid+1,r,rs,L,R)); return re; } // 将线段 x 插入到 [L,R] 当中. void update(int l,int r,int now,int L,int R,int x) { if(l>r) return ; if(l>=L&&r<=R) { if(!tree[now]) tree[now]=x; else { int mid=(l+r)>>1; ll key=dis[idx[mid]]; // x is better than tree[now] if(calc(s[tree[now]], key)>calc(s[x], key)) swap(tree[now], x); // x is bad segment if(calc(s[x], pl[now]) < calc(s[tree[now]], pl[now]) && l!=r) update(l,mid,ls,L,R,x); if(calc(s[x], pr[now]) < calc(s[tree[now]], pr[now]) && l!=r) update(mid+1,r,rs,L,R,x); } mi[now]=get(now); if(l!=r) mi[now]=min(mi[now], min(mi[ls], mi[rs])); return ; } int mid=(l+r)>>1; if(L<=mid) update(l,mid,ls,L,R,x); if(R>mid) update(mid+1,r,rs,L,R,x); pushup(now); } void add(int u,int v,int c) { nex[++edges]=hd[u]; hd[u]=edges; to[edges]=v; val[edges]=c; } void dfs1(int x, int ff) { fa[x]=ff,size[x]=1,dep[x]=dep[ff]+1; for(int i=hd[x];i;i=nex[i]) { int v=to[i]; if(v==ff) continue; dis[v]=dis[x]+val[i]; dfs1(v, x); size[x]+=size[v]; if(size[v]>size[son[x]]) son[x]=v; } } void dfs2(int x, int tp) { top[x]=tp; idx[dfn[x]=++scc]=x; if(son[x]) dfs2(son[x], tp); for(int i=hd[x];i;i=nex[i]) { int v=to[i]; if(v==fa[x]||v==son[x]) continue; dfs2(v, v); } } int get_lca(int x, int y) { while(top[x]!=top[y]) { if(dep[top[x]]>dep[top[y]]) swap(x,y); y=fa[top[y]]; } return dep[x]<dep[y]?x:y; } ll Query(int x, int y) { ll re=inf; while(top[x]!=top[y]) { if(dep[top[x]]>dep[top[y]]) swap(x, y); re=min(re, query(1, n, 1, dfn[top[y]], dfn[y])); // printf("%lld\n",re); y=fa[top[y]]; } if(dep[x]>dep[y]) swap(x, y); return min(re, query(1, n, 1, dfn[x], dfn[y])); } void ins(int x, int y) { while(top[x]!=top[y]) { update(1, n, 1, dfn[top[x]], dfn[x], cn); x=fa[top[x]]; } update(1, n, 1, dfn[y], dfn[x], cn); } int main() { // setIO("input"); scanf("%d%d",&n,&m); for(int i=1;i<n;++i) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } dfs1(1, 0); dfs2(1, 1); build(1, n, 1); for(int i=1;i<=m;++i) { int op,ss,t,a0,b0; scanf("%d",&op); if(op==1) { scanf("%d%d%d%d",&ss,&t,&a0,&b0); int lca=get_lca(ss, t); ++cn; s[cn].k=-a0; s[cn].b=1ll*a0*dis[ss]+b0; ins(ss, lca); ++cn; s[cn].k=a0; s[cn].b=1ll*a0*(dis[ss]-2*dis[lca])+b0; ins(t, lca); } else { scanf("%d%d",&ss,&t); printf("%lld\n",Query(ss, t)); } } return 0; }