前言:
李超树是由杭州学军中学的国家集训队大佬李超发明的,又称超哥线段树,主要功能是用线段树来维护某一点上对应的最高(最低)的线段(或直线)。在数学上来表述,就是处理在某一区间上许多条一次函数的图像 ,维护某一个 的值对应的不同的 的最大值(最小值)。
正文:
其实李超树并不复杂,主要依赖于线段树操作,线段树的主要作用在于维护斜率(k)和截距(b)(也可以只维护k和b的下标)。接下来我们假设要维护区间最大的函数值,讨论一下区间具体维护的是哪个函数以及如何更新区间值的思想。
假设这个区间的标号为root,左端为l,右端为r,中间值为mid,维护的直线斜率为
,截距为
,则该区间维护的直线为使点mid对应的函数值最大的点。换句话说,一个区间维护哪条直线取决于mid,这一点先记下,到后面就会明白的。
我们接下来先讨论更新区间的值的方法,再讨论如何通过区间保存的值求解。
在更新区间时,我们会遇到三种情况:
第一种:旧的直线在区间
完全吊打新的直线(旧的直线在点l和r上都大于新的直线)。
对于这种情况,我们直接返回,不理这条新的直线即可。
第二种:新的直线在区间
完全吊打旧的直线(新的直线在点l和r上都大于旧的直线)。
对于这种情况,把旧的扔掉,将区间维护的值改为新的直线并返回。
第三种:新旧两条直线在区间
相交。
这又要分两种来考虑(好吧其实不止三种情况 ):
一,在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比较难吧 ,我也只是改了一下午而已 。
这题处理询问时,要先预处理每个点的
值,就是每个点通过有边权的边到达根节点的距离。然后对于询问的点
,
,求出它们的LCA,设为
。
不难发现,在路径
上,点
的值为
,与
对应,可得
,
;
在路径
上,点
的值为
,与
对应,可得
,
。
就这样修改直线即可。
#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;
}
好了,李超树大概也讲得差不多了,要想透彻理解一个新的知识点,还是要多刷题啊。