李超线段树学完就忘

前言

决策单调性为啥不能直接上斜优
—— msjing

  • sbmqwm:msjing是个唐必,有些题斜优是完全用不了的,等我去收拾一下她
Waring
  • by sbmqwm
  • msjing是个超级唐必没搞懂就来写,可能有锅,等我有时间给她看看
  • udp on 8.15 11:40:发现在扯淡,紧急修改
  • upd on 8.15 14:45:jade seek发现分讨有大锅,暂时停用并修改

李超树基础

李超树可以干什么

  1. 插入线段/直线
  2. 查询直线 \(x=k\) 上与之有交的线段/直线的最高/最低的编号

原理和实现

  • 显然,实现依据线段树
  • 线段树节点维护 \([l,r]\) 区间的线段
  • 我们按板子题
  • 需要支持插入一条线段,考虑对答案区间的影响,共三大类
1. 完全与答案无交
  • 不用管直接过
2. 完全覆盖答案区间
  • 如果插入线段在原线段上(更优),直接覆盖,欸,我直接用代码画图
现线段
 |_\  /|
     / |
    /  |
   /   |
  /    |
 /     |
/      |
--------<-原线段
  • 反之直接不管
原线段
 |_\  /|
     / |
    /  |
   /   |
  /    |
 /     |
/      |
--------<-现线段
与答案区间线段有交
  • 画一个
\          /<-原线段
 \        /
  \      /
   \    /
    \  /
     \/
     /\
    /  \
   /    \
  /      \
 /        \
/          \<-现线段
  • 鉴于msjing分讨锅太大了,所以msjing打算直接粘oiwiki的
粘的
  • 具体来说,设当前区间的中点为 \(m\),我们拿新线段 \(f\) 在中点处的值与原最优线段 \(g\) 在中点处的值作比较
  • 如果新线段 \(f\) 更优,则将 \(f\)\(g\) 交换。那么现在考虑在中点处 \(f\) 不如 \(g\) 优的情况:
  1. 若在左端点处 \(f\) 更优,那么 \(f\)\(g\) 必然在左半区间中产生了交点,\(f\) 只有在左区间才可能优于 \(g\),递归到左儿子中进行下传
  2. 若在右端点处 \(f\) 更优,那么 \(f\)\(g\) 必然在右半区间中产生了交点,\(f\) 只有在右区间才可能优于 \(g\),递归到右儿子中进行下传
  3. 若在左右端点处 \(g\) 都更优,那么 \(f\) 不可能成为答案,不需要继续下传
  • 除了这两种情况之外,还有一种情况是\(f\)\(g\) 刚好交于中点,在程序实现时可以归入中点处 \(f\) 不如 \(g\) 优的情况,结果会往 \(f\) 更优的一个端点进行递归下传
  • 最后将 \(g\) 作为当前区间的懒标记
  • 实现上,当满足前两种情况时,直接停止并打 \(tag\)
  • 你会发现 \(tag\) 并没有下放,所以在查询时要处理一下
查询
  • 李超树只支持单点查,但考虑到 \(tag\) 并没有下放,所以我们要对每个包含 \(pos\) 的节点都看看答案是否更优并更新答案
  • 代码的话,找一个漂亮的代码看看,msjing一直调不出来
  • upd on 8.15 16:15:msjing拉着wang79调了一个小时发现是没换 \(x_0\)\(x_1\)
点击查看代码
#include<bits/stdc++.h>
#define ldb long double
#define pll pair<long double,long double>
#define fi first
#define se second
using namespace std;
constexpr int maxn=1e5+10,mo1=39989,mo2=1e9;
constexpr ldb eps=1e-8;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0' || ch>'9')
    {
        if (ch == '-') f=-1;
        ch=getchar();
    }
    while (ch>='0' && ch<='9')
    {
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int n;
pll lik[maxn];
int id;
void getkb(int A,int B,int C,int D){
    if (A>C) swap(A,C),swap(B,D);
    id++;
    if (C == A) lik[id]={0,max(B,D)};
	else lik[id].fi=(ldb)(D-B)/(ldb)(C-A),lik[id].se=(ldb)D-(ldb)C*lik[id].fi;
}
namespace lcsgt
{
    #define ls (rt<<1)
    #define rs (rt<<1|1)
    #define mid ((t[rt].l+t[rt].r)>>1)
    struct _ {int l,r,v;}t[maxn<<2];
    ldb gety(int id,int x) {return lik[id].fi*(ldb)x+lik[id].se;}
    int cmp(int u,int v,int x)
    {
        if (gety(u,x)-gety(v,x)>eps) return 1;
        if (gety(v,x)-gety(u,x)>eps) return 0;
        return u<v;
    }
    void Build(int rt,int l,int r)
    {
        t[rt].l=l,t[rt].r=r;
        if (l == r) return;
        Build(ls,l,mid),Build(rs,mid+1,r);
    }
    void Ins(int rt,int x)
    {
        if (cmp(x,t[rt].v,t[rt].l) && cmp(x,t[rt].v,t[rt].r))
            t[rt].v=x;
        if (!cmp(x,t[rt].v,t[rt].l) && !cmp(x,t[rt].v,t[rt].r))
            return;
        if (cmp(x,t[rt].v,mid))
            swap(x,t[rt].v);
        if (cmp(x,t[rt].v,t[rt].l))
            Ins(ls,x);
        if (cmp(x,t[rt].v,t[rt].r))
            Ins(rs,x);
    }
    void Upd(int rt,int L,int R,int x)
    {
        if (L<=t[rt].l && t[rt].r<=R) return Ins(rt,x),void();
        if (L<=mid) Upd(ls,L,R,x);
        if (R>mid) Upd(rs,L,R,x);
    }
    int que(int rt,int x)
    {
        if (t[rt].l == t[rt].r)
            return t[rt].v;
        int res=0;
        if (x<=mid) res=que(ls,x);
        else res=que(rs,x);
        if (cmp(t[rt].v,res,x))
            res=t[rt].v;
        return res;
    }
}using namespace lcsgt;
signed main()
{
    Build(1,1,mo1);
    n=read();
    int lans=0;
    for (int i=1;i<=n;i++)
    {
        int op=read();
        if (!op)
        {
            int k=read();
            int x=(k+lans-1)%mo1+1;
            lans=que(1,x);
            printf("%d\n",lans);
        }
        else
        {
            int x0=read(),y0=read(),x1=read(),y_1=read();
            x0=(x0+lans-1)%mo1+1;
            y0=(y0+lans-1)%mo2+1;
            x1=(x1+lans-1)%mo1+1;
            y_1=(y_1+lans-1)%mo2+1;
            getkb(x0,y0,x1,y_1);
            // cerr << lik[id].fi << " " << lik[id].se << endl;
            // cerr << "---------------------" << endl;
            // cerr << x0 << " " << x1 << " " << id << endl;
            if (x0>x1) swap(x0,x1);
            Upd(1,x0,x1,id);
            // for (int i=1;i<=10;i++)
            //     cerr << que(1,i) << " ";
            // cerr << endl;
            // cerr << "---------------------" << endl;
        }
    }
	return 0;
}

应用

  • 李超树可以干什么呢?
  • 决策单调性地皮优化/上树维护

例题P4069 [SDOI2016] 游戏

  • 这题是上树的李超树
  • 首先我们要先把 long long 挂上
  • 我们先考虑带权求树上两点距离,很好做,我们直接做一遍 \(dfs\) 求出每个点到根的距离,对于插入操作的 \(s\)\(t\),我们可以根据题意要求的柿子进行化简:

\[\sum\limits_{i=s}^{t} \max(a \times (dis_i - dis_s) + b) \]

  • 由于上树,且直接求复杂度太高,所以我们可以求一下 \(lca(s,t)\),算出 \(s \to lca\) 的表达式为一条下述直线:

\[y = -a \times dis_i + a \times dis_s + b \]

  • \(t \to lca\) 也可求:

\[y = a\times dis_i + a \times (dis_s − 2 \times dis_{lca}) \]

  • 行了,这个东西就可以用李超树维护了
  • 等等,可以维护个 \(\mathcal{d**n}\),在树上咋搞
  • 欸,直接用重剖就行了,求出的 \(dfn\)\(rnk\) 一正一反,可以很方便维护树上直线的编号和做修改查询,\(lca\) 顺手跑一下就行
  • 没了
  • 完整代码 \(rand\) 一个题解写的都比我好,不给了

后记

  • 模拟退火太难了哦哦哦哦哦哦哦哦哦!
posted @ 2026-08-15 11:15  msjing  阅读(14)  评论(2)    收藏  举报