李超线段树
李超线段树
用途:给定 $\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;
}

浙公网安备 33010602011771号