李超树

李超数支持动态插入线段/直线,查询单点极值。

算法思想

排除不可能成为最优解的,维护在当前区间能成为最优解的线段,即该线段在当前区间的某个取值上有最优解。

查询的时间复杂度是 \(O(\log n)\) 的,修改时通过替换和下放,也能达到 \(O(\log n)\) 的复杂度,区间修改能达到 \(O(\log^2 n)\),(证明:最多把每条直线的值域分到 \(\log n\) 个区间,每个区间最多把标记下传 \(\log n\)层,所以区间修改的复杂度为 \(O(\log ^2 n\))。

对信息进行维护时,可以使用斜率和线段的中点取值进行分类讨论。( \(eg\)

(在类似于这样的图上面插入新线段

设当前区间为 \([L,R]\) ,当前区间的中点为 \(mid\)

  1. 当前区间没有线段,新鲜段直接放入该区间。

  2. 新线段的斜率小于旧线段的斜率:

    • 若新线段在中点处的取值 \(>\) 旧线段的取值,则新线段在 \([L,mid]\) 上的取值一定比旧线段更优,但旧线段可能在 \([mid+1,R]\) 上的取值更优,用新线段替换当前节点的旧线段,将旧线段下放至 \([mid+1,R]\) 区间并递归更新答案。
    • 若新线段在中点处的取值 \(<\) 旧线段的取值,则旧线段在 \([mid+1,R]\) 上的取值一定比新线段更优,但新线段可能在 \([L,mid]\) 上的取值更优,将新线段下放至 \([L,mid]\) 区间并递归更新答案。
  3. 新线段的斜率大于旧线段的斜率:

    • 若新线段在中点处的取值 \(>\) 旧线段的取值,则新线段在 \([mid+1,R]\) 上的取值一定比旧线段更优,但旧线段可能在 \([L,mid]\) 上的取值更优,用新线段替换当前节点的取值,将旧线段下放至 \([L,mid]\) 区间并递归更新答案。
    • 若新线段在中点处的取值 \(<\) 旧线段的取值,则旧线段在 \([L,mid]\) 上的取值一定比新线段更优,但新线段可能在 \([mid+1,R]\) 上的取值更优,将新线段下放至 \([mid+1,R]\) 区间并递归更新答案。
  4. 新旧线段斜率相同,则比较截距 \(b\),截距大的取值更优,直接用截距大的。

例题

Segment

李超树维护线段。

code!
const int N=1e5+10;
const int mod=1e9;
const double eps=1e-9;//误差
int n,op,lans,x,x_1,x_2,y_1,y_2,cnt,t[N<<2];
double k[N],b[N];

void get_val(int xx,int yy,int X,int Y){//计算斜率和截距
	++cnt;
	if(xx==X){
		k[cnt]=0; b[cnt]=max(yy,Y);
	}
	else {
		k[cnt]=1.0*(Y-yy)/(X-xx); b[cnt]=Y-k[cnt]*X;
	}
	return ;
}

int cmp(double x,double y){
	if(x-y>eps) return 1;
	if(y-x>eps) return -1;
	return 0;
}

double calc(int id,int v){//计算值
	return k[id]*v+b[id];
}

void upd(int k,int l,int r,int u){
	int &v=t[k],mid=(l+r)>>1;
	int line_k=cmp(calc(u,mid),calc(v,mid));
	if(line_k==1||(!line_k&&u<v)) swap(u,v);
	int vl=cmp(calc(u,l),calc(v,l)),vr=cmp(calc(u,r),calc(v,r));
	if(vl==1||(!vl&&u<v)) upd(k<<1,l,mid,u);
	if(vr==1||(!vr&&u<v)) upd(k<<1|1,mid+1,r,u);
}

void update(int k,int l,int r,int fl,int fr,int nm){
	if(l>=fl&&r<=fr){
		upd(k,l,r,nm);
		return ;
	}
	int mid=(l+r)>>1;
	if(fl<=mid) update(k<<1,l,mid,fl,fr,nm);
	if(fr>mid) update(k<<1|1,mid+1,r,fl,fr,nm);
	return ;
}

pdi Max(pdi x,pdi y){
	int f=cmp(x.first,y.first);
	if(f==1) return x;
	if(f==-1) return y;
	return x.second<y.second?x:y;
}

pdi query(int k,int l,int r,int v){
	if(r<v||l>v) {return {0,0};}
	int mid=(l+r)>>1;
	double vl=calc(t[k],v);
	if(l==r) {return {vl,t[k]};}
		return Max({vl,t[k]},Max(query(k<<1,l,mid,v),query(k<<1|1,mid+1,r,v)));
}

signed main(){
	n=read();
	for(int i=1;i<=n;++i){
		op=read();
		if(op==1){
			x_1=read();y_1=read();x_2=read();y_2=read();
			x_1=(x_1+lans-1+39989)%39989+1;
			x_2=(x_2+lans-1+39989)%39989+1;
			y_1=(y_1+lans-1+mod)%mod+1;
			y_2=(y_2+lans-1+mod)%mod+1;
			if(x_1>x_2) {swap(x_1,x_2); swap(y_1,y_2);}
			get_val(x_1,y_1,x_2,y_2);
			update(1,1,N,x_1,x_2,cnt);
		}
		else {
			x=read();
			x=(x+lans-1+39989)%39989+1;
			cout<<(lans=query(1,1,N,x).second)<<'\n';
		}
	}
	return 0;
}

Bule Marty开公司

李超树维护直线。

code!
const int N=1e5+10;
const int M=5e4+1;
const double eps=1e-9;
int n,x,cnt,t[N<<2];
double k[N],b[N];
string s;
int cmp(double x,double y){
	if(x-y>eps) return 1;
	if(y-x>eps) return -1;
	return 0;
}

double calc(int id,int v){
	return k[id]*(v-1)+b[id];
}

void update(int x,int l,int r,int v){
	if(l==r){if(calc(t[x],l)<calc(v,l)) t[x]=v; return ;}
	if(!t[x]) {t[x]=v;return ;}
	else {
		int mid=(l+r)>>1,u=t[x];
		if(k[u]>k[v]){
			if(cmp(calc(u,mid),calc(v,mid))==1) update(x<<1,l,mid,v);
			else {update(x<<1|1,mid+1,r,t[x]);t[x]=v;}
		}
		else if(k[u]<k[v]){
			if(cmp(calc(u,mid),calc(v,mid))==1) update(x<<1|1,mid+1,r,v);
			else {update(x<<1,l,mid,t[x]);t[x]=v;}
		}
		else if(b[v]>b[u]) t[x]=v;
	}
	return ;
}

double query(int x,int l,int r,int v){
	if(l==r){return calc(t[x],l);}
	double res=calc(t[x],v);
	int mid=(l+r)>>1;
	if(v<=mid) res=max(res,query(x<<1,l,mid,v));
	else res=max(res,query(x<<1|1,mid+1,r,v));
	return res;
}

signed main(){
	n=read();
	for(int i=1;i<=n;++i){
		cin>>s;	
		if(s[0]=='P'){
			++cnt;
			cin>>b[cnt]>>k[cnt];
			update(1,1,M,cnt);
		}
		else {
			x=read();
			cout<<(int)query(1,1,M,x)/100<<'\n';
		}
	}
	return 0;
}

游戏

码量巨大,非常难调/ll。

思路大概是,树剖维护链,然后大力推式子,用李超树维护最小值。

式子是:

\(ans=max(a\times(dis[s]-dis[i])+b)\)

可以发现上述式子是一次函数,于是我们可以求出两点间的 \(LCA\) ,分别进行维护

\(a\times dis[s]-a\times dis[i] \Rightarrow - a \times dis[i] + a \times dis[s]\)

斜率为 \(-a\),截距为 \(a\times dis[s]\)

\(a \times dis[s] - a\times dis[lca] + a\times dis[i] - a\times dis[lca] \Rightarrow a\times dis[i] + a\times(dis[s]-2\times dis[lca])\)

斜率为 \(a\),截距为 \(a\times(dis[s]-2\times dis[lca])\)

code:
const int N=1e5+10;
const int inf=123456789123456789;
const int eps=1e-9;
int n,m,op,x,y,z,e,cnt,tot,head[N],t[N<<4],k[N<<4],b[N<<4],mn[N<<4];
int num,o[N],top[N],son[N],siz[N],bel[N],dep[N],fa[N],dis[N];
struct node{
	int u,v,w,nxt;
}a[N<<1];
void add(int u,int v,int w){
	a[++cnt]=(node){u,v,w,head[u]};
	head[u]=cnt;
}
int clac(int id,int v) {
	return k[id]*dis[bel[v]]+b[id];
}
int cmp(int x,int y) {
	if(x-y>eps) return 1;
	if(y-x>eps) return -1;
	return 0;
}
void push_up(int x,int l,int r) {//
	if(l==r) {mn[x]=min(mn[x],min(clac(t[x],l),clac(t[x],r)));}
	else {mn[x]=min(min(clac(t[x],l),clac(t[x],r)),min(mn[x],min(mn[x<<1],mn[x<<1|1])));}
}
void upd(int x,int l,int r,int v){
	int &u=t[x],mid=(l+r)>>1;
	if(clac(v,mid)<clac(u,mid)) swap(u,v);
	if(clac(v,l)<clac(u,l)) upd(x<<1,l,mid,v);
	if(clac(v,r)<clac(u,r)) upd(x<<1|1,mid+1,r,v);
}
void update(int x,int l,int r,int fl,int fr,int v) {
	if(l>=fl&&r<=fr) {
		upd(x,l,r,v);
		push_up(x,l,r);
		return ;
	}
	int mid=(l+r)>>1;
	if(fl<=mid) update(x<<1,l,mid,fl,fr,v);
	if(fr>mid) update(x<<1|1,mid+1,r,fl,fr,v);
	push_up(x,l,r);
}
void Update(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		update(1,1,n,o[top[x]],o[x],tot);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,1,n,o[x],o[y],tot);
}
int query(int x,int l,int r,int fl,int fr){
	if(l>=fl&&r<=fr) {
		return mn[x];
	}
	int mid=(l+r)>>1,res=inf;
	if(b[t[x]]!=inf) res=min(res,min(clac(t[x],max(fl,l)),clac(t[x],min(fr,r))));//
	if(fl<=mid) res=min(res,query(x<<1,l,mid,fl,fr));
	if(fr>mid) res=min(res,query(x<<1|1,mid+1,r,fl,fr));
	return res;
}
int Query(int x,int y){
	int res=inf;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=min(res,query(1,1,n,o[top[x]],o[x])),x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return min(res,query(1,1,n,o[x],o[y]));
}
void build(int x,int l,int r){
	t[x]=1;mn[x]=inf;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	return ;
}
int dfs1(int x,int fat){
	dep[x]=dep[fat]+1;
	fa[x]=fat;
	siz[x]=1;
	int Son=-1;
	for(int i=head[x];i;i=a[i].nxt){
		int v=a[i].v;
		if(v==fat) continue;
		dis[v]=dis[x]+a[i].w;
		siz[x]+=dfs1(v,x);
		if(siz[v]>Son) {
			son[x]=v;
			Son=siz[v];
		}
	}
	return siz[x];
}
void dfs2(int x,int topf){
	top[x]=topf;
	o[x]=++num;
	bel[num]=x;
	if(!son[x])
		return ;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=a[i].nxt){
		int v=a[i].v;
		if(!o[v])
			dfs2(v,v);
	}
}
int LCA(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}
signed main(){
	n=read(); m=read();
	for(int i=1;i<n;++i) {
		x=read(); y=read(); z=read();
		add(x,y,z);
		add(y,x,z);
	}
	dfs1(1,0);
	dfs2(1,1);
	k[++tot]=0; b[tot]=inf;
	build(1,1,n);
	for(int i=1;i<=m;++i) {
		op=read();
		if(op==1) {
			x=read();e=read();y=read();z=read();
			int lca=LCA(x,e);
			k[++tot]=-y;b[tot]=y*dis[x]+z;Update(x,lca);
			k[++tot]=y;b[tot]=y*(dis[x]-(2*dis[lca]))+z;Update(e,lca);
		}
		else {
			x=read();e=read();
			cout<<Query(x,e)<<'\n';
		}
	}
	return 0;
}

未完待续(?

posted @ 2023-08-09 16:02  XYini  阅读(64)  评论(3)    收藏  举报