数据结构-李超树学习小结

数据结构-李超树学习小结

前言:

李超树是由杭州学军中学的国家集训队大佬李超发明的,又称超哥线段树,主要功能是用线段树来维护某一点上对应的最高(最低)的线段(或直线)。在数学上来表述,就是处理在某一区间上许多条一次函数的图像 y = k i ∗ x + b y=k_i*x+b y=kix+b,维护某一个 x x x的值对应的不同的 y y y的最大值(最小值)。

正文:

其实李超树并不复杂,主要依赖于线段树操作,线段树的主要作用在于维护斜率(k)和截距(b)(也可以只维护k和b的下标)。接下来我们假设要维护区间最大的函数值,讨论一下区间具体维护的是哪个函数以及如何更新区间值的思想。
假设这个区间的标号为root,左端为l,右端为r,中间值为mid,维护的直线斜率为 k [ r o o t ] k[root] k[root],截距为 b [ r o o t ] b[root] b[root],则该区间维护的直线为使点mid对应的函数值最大的点。换句话说,一个区间维护哪条直线取决于mid,这一点先记下,到后面就会明白的。
我们接下来先讨论更新区间的值的方法,再讨论如何通过区间保存的值求解。
在更新区间时,我们会遇到三种情况:
第一种:旧的直线在区间 [ l , r ] [l,r] [l,r]完全吊打新的直线(旧的直线在点l和r上都大于新的直线)。

图1
对于这种情况,我们直接返回,不理这条新的直线即可。
第二种:新的直线在区间 [ l , r ] [l,r] [l,r]完全吊打旧的直线(新的直线在点l和r上都大于旧的直线)。
图2
对于这种情况,把旧的扔掉,将区间维护的值改为新的直线并返回
第三种:新旧两条直线在区间 [ l , r ] [l,r] [l,r]相交。
图3
这又要分两种来考虑(好吧其实不止三种情况 ):
,在mid上旧的直线胜过新的直线。
根据上文的定义,这个区间保存的直线还是不变,由于这里相交导致左右区间的情况不同需要拆分向下特殊处理:
当在l上新的直线吊打旧的直线,则左区间上新的直线可能更优,将新的直线传到当前的左儿子区间上;否则,因为两条直线相交,在r上新的直线必然吊打旧的直线,将新的直线传到当前的右儿子区间上。
,在mid上新的直线胜过旧的直线。
根据上文的定义,这个区间保存的直线变为新的直线,旧的直线在哪边占优,将其传到对应方向的儿子区间即可。
第三种情况还可以按斜率讨论,请自行思考或看下面第一题代码。
自此,我们已经明白了李超树的修改操作了。接下来就是它的查询了。

通过对上述修改操作的仔细观察,不难发现某一区间与它左或右儿子保存的直线可能不同,有的儿子区间可能根本没有保存直线。
对于第一点,原因在于该区间优先考虑它的mid对应的值,可能与儿子区间发生冲突,只好让两个区间保存不一样的直线。
对于第二点,原因在于儿子区间与父区间根本没有冲突,就让父区间代为保存了。
从这可以看出,在查询时只要在线段树上从根节点往下搜索自己查询的点或区间,从每个便历到的区间对应的直线所得的函数值取最大值即可。

模板题:

基本操作讲完了,如果觉的还不够清楚,就看一看这三道模板题吧。
Blue Mary开公司https://www.luogu.org/problem/P4254

这是非常直白的模板题了,直接上代码吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 100005
#define M 50005
using namespace std;
int n , cnt;
double k[N] , b[N];
char s[15];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline double w( int id , int pos ) {
	return k[id] * ( pos - 1.0 ) + b[id];
}
struct SegmentTree {
	#define ls root << 1
	#define rs root << 1 | 1
	int t[M << 2];
	inline void update( int root , int l , int r , int p ) {
		if ( w( p , l ) <= w( t[root] , l ) && w( p , r ) <= w( t[root] , r ) ) {
			return;//情况1
		}
		if ( w( p , l ) > w( t[root] , l ) && w( p , r ) > w( t[root] , r ) ) {
			t[root] = p;//情况2
			return;
		}
		int mid = ( l + r ) >> 1;//情况3
		if ( k[p] > k[t[root]] ) {//斜率讨论法
			if ( w( p , mid ) > w( t[root] , mid ) ) {
				update( ls , l , mid , t[root] );
				t[root] = p;
			} else {
				update( rs , mid + 1 , r , p );
			}
		} else {
			if ( w( p , mid ) > w( t[root] , mid ) ) {
				update( rs , mid + 1 , r , t[root] );
				t[root] = p;
			} else {
				update( ls , l , mid , p );
			}
		}
	}
	inline double query( int root , int l , int r , int pos ) {
		if ( l == r ) {
			return w( t[root] , pos );
		}
		int mid = ( l + r ) >> 1;
		//查询时取便历到的最值
		if ( pos <= mid ) {
			return max( w( t[root] , pos ) , query( ls , l , mid , pos ) );
		} else {
			return max( w( t[root] , pos ) , query( rs , mid + 1 , r , pos ) );
		}
	}
}tree;
int main () {
	read(n);
	int tem;
	while ( n-- ) {
		scanf("%s",s);
		if ( s[0] == 'P' ) {
			cnt++;
			scanf("%lf%lf",&b[cnt],&k[cnt]);
			tree.update( 1 , 1 , M , cnt ); 
		} else {
			read(tem);
			printf("%d\n",(int)tree.query( 1 , 1 , M , tem ) / 100 );
		}
	}
	return 0;
}

Segmenthttps://www.luogu.org/problem/P4097
这题也是模板,只是相比上题维护的对象从直线变为线段,修改时限制区间即可。同时,本题可能给出一条垂直于x轴的线段,需要特殊处理。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define N 100005
#define M 40005
#define ll long long
using namespace std;
int n;
const double eps = 1e-8;
const int mod1 = 39989;
const int mod2 = 1e9;
double k[N] , b[N];
template <typename T>
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline void swap( int & a , int & b ) {
	a ^= b ^= a ^= b;
	return;
}
inline double w( int id , int x ) {
	return (double)x * k[id] + b[id];
}
inline int max( int a , int b ) {
	return a > b ? a : b;
}
inline bool judge( int id1 , int id2 , int x ) {
	double f1 = w( id1 , x );
	double f2 = w( id2 , x );
	if ( fabs( f1 - f2 ) <= eps ) {
		return id1 > id2;
	} else {
		return f1 < f2;
	}
}//if id1 is useless
struct SegmentTree{
	#define ls root << 1
	#define rs root << 1 | 1
	int t[M << 2];
	inline void update( int root , int l , int r , int x , int y , int p ) {
		int mid = ( l + r ) >> 1;
		if ( x <= l && r <= y ) {
			if ( judge( p , t[root] , l ) && judge( p , t[root] , r ) ) {
				return;//情况1
			}
			if ( judge( t[root] , p , l ) && judge( t[root] , p , r ) ) {
				t[root] = p;//情况2
				return;
			}
			//情况3
			if ( judge( t[root] , p , mid ) ) {
				swap( p , t[root] );//正文的方法
			}
			if ( judge( t[root] , p , l ) ) {
				update( ls , l , mid , x , y , p );
			} else {
				update( rs , mid + 1 , r , x , y , p );
			}
			return;
		}
		if ( x <= mid ) {
			update( ls , l , mid , x , y , p );
		}
		if ( y >= mid + 1 ) {
			update( rs , mid + 1 , r , x , y , p );
		}
		return;
	}
	inline int query( int root , int l , int r , int pos ) {
		if ( l == r ) {
			return t[root];
		}
		int mid = ( l + r ) >> 1;
		int ret = 0;
		if ( mid >= pos ) {
			ret = query( ls , l , mid , pos );
		} else {
			ret = query( rs , mid + 1 , r , pos );
		}
		if ( judge( ret , t[root] , pos ) ) {
			ret = t[root];
		}
		return ret;
	}
}tree;
int main () {
	read(n);
	int op , x , x0 , x1 , y0 , y1 , lastans , cnt;
	lastans = 0;
	cnt = 0;
	memset( tree.t , 0 , sizeof( tree.t ) );
	while ( n-- ) {
		read(op);
		switch(op) {
			case 0 : {
				read(x);
				x = ( x + lastans - 1 + mod1 ) % mod1 + 1;
				lastans = tree.query( 1 , 1 , mod1 , x );
				printf("%d\n",lastans); 
				break;
			}
			case 1 : {
				read(x0);
				read(y0);
				read(x1);
				read(y1);
				x0 = ( x0 + lastans - 1 + mod1 ) % mod1 + 1;
				x1 = ( x1 + lastans - 1 + mod1 ) % mod1 + 1;
				y0 = ( (ll)y0 + lastans - 1 + mod2 ) % mod2 + 1;
				y1 = ( (ll)y1 + lastans - 1 + mod2 ) % mod2 + 1;
				if ( x0 > x1 ) {
					swap( x0 , x1 );
					swap( y0 , y1 );
				}
				cnt++;
				if ( x0 == x1 ) {
					k[cnt] = 0;
					b[cnt] = max( y0 , y1 );
				} else {
					k[cnt] = (double)( y1 - y0 ) / (double)( x1 - x0 );
					b[cnt] = (double)y1 - (double)x1 * k[cnt];
				}
				tree.update( 1 , 1 , mod1 , x0 , x1 , cnt ); 
				break;
			}
		}
	}
	return 0;
}

游戏https://www.luogu.org/problem/P4069
这题是李超树加上树剖,其实还是模板,只是码量过大debug比较难吧我也只是改了一下午而已
这题处理询问时,要先预处理每个点的 d i s dis dis值,就是每个点通过有边权的边到达根节点的距离。然后对于询问的点 s s s t t t,求出它们的LCA,设为 w w w
不难发现,在路径 s − w s-w sw上,点 i i i的值为 ( d i s [ i ] − d i s [ s ] ) ∗ a + b = − a ∗ d i s [ i ] + d i s [ s ] ∗ a + b (dis[i]-dis[s])*a+b=-a*dis[i]+dis[s]*a+b (dis[i]dis[s])a+b=adis[i]+dis[s]a+b,与 y = k ∗ x + b y=k*x+b y=kx+b对应,可得 k = − a k=-a k=a b = d i s [ s ] ∗ a + b b=dis[s]*a+b b=dis[s]a+b
在路径 t − w t-w tw上,点 i i i的值为 ( d i s [ i ] + d i s [ s ] − 2 ∗ d i s [ w ] ) ∗ a + b = a ∗ d i s [ i ] + ( d i s [ s ] − 2 ∗ d i s [ w ] ) ∗ a + b (dis[i]+dis[s]-2*dis[w])*a+b=a*dis[i]+(dis[s]-2*dis[w])*a+b (dis[i]+dis[s]2dis[w])a+b=adis[i]+(dis[s]2dis[w])a+b,与 y = k ∗ x + b y=k*x+b y=kx+b对应,可得 k = a k=a k=a b = ( d i s [ s ] − 2 ∗ d i s [ w ] ) ∗ a + b b=(dis[s]-2*dis[w])*a+b b=(dis[s]2dis[w])a+b
就这样修改直线即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define N 100005 
using namespace std;
const ll inf = 123456789123456789ll;
int n , m , bcnt;
int head[N] , dep[N] , siz[N] , f[N] , son[N] , seg[N] , rev[N] , top[N];
ll dis[N];
struct node{
	int next;
	int to;
	int val;
}str[N << 1];
template < typename T >
inline void read( T & res ) {
	res = 0;
	T pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline ll min( ll a , ll b ) {
	return a < b ? a : b;
}
inline ll max( ll a , ll b ) {
	return a > b ? a : b;
}
inline void swap( int & a , int & b ) {
	a ^= b ^= a ^= b;
	return;
}
struct SegmentTree {
	#define ls root << 1
	#define rs root << 1 | 1
	ll k[N << 2] , b[N << 2] , minn[N << 2];
	inline void build( int root , int l , int r ) {
		if ( l == r ) {
			b[root] = minn[root] = inf;
			return;
		}
		int mid = ( l + r ) >> 1;
		b[root] = minn[root] = inf;
		build( ls , l , mid );
		build( rs , mid + 1 , r );
		return;
	}
	inline ll w( int id , ll x ) {
		return k[id] * x + b[id];
	}
	inline void update( int root , int l , int r , int x , int y , ll kk , ll bb ) {
		int mid = ( l + r ) >> 1;
		if ( x <= l && r <= y ) {
			ll lx = dis[rev[l]];
			ll rx = dis[rev[r]];
			ll mx = dis[rev[mid]];
			ll l0 = w( root , lx );
			ll l1 = lx * kk + bb;
			ll r0 = w( root , rx );
			ll r1 = rx * kk + bb;
			if ( l0 <= l1 && r0 <= r1 ) {
				return;//情况1
			}
			if ( l0 > l1 && r0 > r1 ) {
				k[root] = kk;
				b[root] = bb;
				minn[root] = min( minn[root] , min( l1 , r1 ) );
				return;//情况2
			}
			ll m0 = w( root , mx );
			ll m1 = mx * kk + bb;
			if ( k[root] < kk ) {//斜率法
				if ( m1 < m0 ) {
					update( rs , mid + 1 , r , x , y , k[root] , b[root] );
					k[root] = kk;
					b[root] = bb;
				} else {
					update( ls , l , mid , x , y , kk , bb );
				}
			} else {
				if ( m1 < m0 ) {
					update( ls , l , mid , x , y , k[root] , b[root] );
					k[root] = kk;
					b[root] = bb;
				} else {
					update( rs , mid + 1 , r , x , y , kk , bb );
				}
			}
			minn[root] = min( minn[root] , min( l1 , r1 ) );
			minn[root] = min( minn[root] , min( minn[ls] , minn[rs] ) );
			return;
		}
		if ( x <= mid ) {
			update( ls , l , mid , x , y , kk , bb );
		}
		if ( y >= mid + 1 ) {
			update( rs , mid + 1 , r , x , y , kk , bb );
		}
		minn[root] = min( minn[root] , min( minn[ls] , minn[rs] ) );
		return;
	}
	inline ll query( int root , int l , int r , int x , int y ) {
		if ( x <= l && r <= y ) {
			return minn[root];
		}
		ll res = inf;
		if ( b[root] != inf ) {
			int lx = max( l , x );
			int rx = min( r , y );
			res = min( w( root , dis[rev[lx]] ) , w( root , dis[rev[rx]] )  );
		}
		int mid = ( l + r ) >> 1;
		if ( x <= mid ) {
			res = min( res , query( ls , l , mid , x , y ) );
		}
		if ( y >= mid + 1 ) {
			res = min( res , query( rs , mid + 1 , r , x , y ) );
		}
		return res;
	}
}tree;
inline void insert( int from , int to , int val ) {
	str[++bcnt].next = head[from];
	head[from] = bcnt;
	str[bcnt].to = to;
	str[bcnt].val = val;
	return;
}
inline void dfs1( int cur , int fa ) {
	f[cur] = fa;
	siz[cur] = 1;
	dep[cur] = dep[fa] + 1;
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( sn == fa ) {
			continue;
		}
		dis[sn] = dis[cur] + str[i].val;
		dfs1( sn , cur );
		siz[cur] += siz[sn];
		if ( siz[sn] > siz[son[cur]] ) {
			son[cur] = sn;
		}
	}
	return;
}
inline void dfs2( int cur , int fa ) {
	if ( son[cur] ) {
		seg[son[cur]] = ++seg[0];
		rev[seg[0]] = son[cur];
		top[son[cur]] = top[cur];
		dfs2( son[cur] , cur );
	}
	for ( int i = head[cur] ; i ; i = str[i].next ) {
		int sn = str[i].to;
		if ( top[sn] ) {
			continue;
		}
		top[sn] = sn;
		seg[sn] = ++seg[0];
		rev[seg[0]] = sn;
		dfs2( sn , cur );
	}
	return;
}
inline int lca( int u , int v ) {
	while ( top[u] != top[v] ) {
		if ( dep[top[u]] > dep[top[v]] ) {
			u = f[top[u]];
		} else {
			v = f[top[v]];
		}
	}
	if ( dep[u] > dep[v] ) {
		return v;
	} else {
		return u;
	}
}
inline ll query( int u , int v ) {
	ll res = inf;
	while ( top[u] != top[v] ) {
		if ( dep[top[v]] > dep[top[u]] ) {
			swap( u , v );
		}
		res = min( res , tree.query( 1 , 1 , seg[0] , seg[top[u]] , seg[u] ) );
		u = f[top[u]];
	}
	if ( dep[v] > dep[u] ) {
		swap( u , v );
	}
	res = min( res , tree.query( 1 , 1 , seg[0] , seg[v] , seg[u] ) );
	return res;
}
inline void modify( int u , int w , ll kk , ll bb ) {
	while ( top[u] != top[w] ) {
		tree.update( 1 , 1 , seg[0] , seg[top[u]] , seg[u] , kk , bb );
		u = f[top[u]];
	}
	tree.update( 1 , 1 , seg[0] , seg[w] , seg[u] , kk , bb );
	return;
}
int main () {
	read(n);
	read(m);
	int u , v , w , op , s , t;
	ll a , b;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		read(u);
		read(v);
		read(w);
		insert( u , v , w );
		insert( v , u , w );
	}
	dis[1] = 0;
	dfs1( 1 , 0 );
	top[1] = 1;
	seg[1] = 1;
	rev[1] = 1;
	seg[0] = 1;
	dfs2( 1 , 0 );
	tree.build( 1 , 1 , seg[0] );
	for ( int i = 1 ; i <= m ; ++i ) {
		read(op);
		switch(op) {
			case 1 : {
				read(s);
				read(t);
				read(a);
				read(b);
				w = lca( s , t );
				modify( s , w , -a , a * dis[s] + b );
				modify( t , w , a , a * dis[s] - a * ( dis[w] << 1 ) + b );
				break;
			}
			case 2 : {
				read(s);
				read(t);
				printf("%lld\n",query( s , t ));
				break;
			}
		}
	} 
	return 0;
}

好了,李超树大概也讲得差不多了,要想透彻理解一个新的知识点,还是要多刷题啊。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值