李超线段树学完就忘
前言
决策单调性为啥不能直接上斜优
—— msjing
- sbmqwm:msjing是个唐必,有些题斜优是完全用不了的,等我去收拾一下她
Waring
- by sbmqwm
- msjing是个超级唐必没搞懂就来写,可能有锅,等我有时间给她看看
- udp on 8.15 11:40:发现在扯淡,紧急修改
- upd on 8.15 14:45:jade seek发现分讨有大锅,暂时停用并修改
李超树基础
李超树可以干什么
- 插入线段/直线
- 查询直线 \(x=k\) 上与之有交的线段/直线的最高/最低的编号
原理和实现
显然,实现依据线段树- 线段树节点维护 \([l,r]\) 区间的线段
- 我们按板子题讲
- 需要支持插入一条线段,考虑对答案区间的影响,共三大类
1. 完全与答案无交
- 不用管直接过
2. 完全覆盖答案区间
- 如果插入线段在原线段上(更优),直接覆盖,欸,我直接用代码画图
现线段
|_\ /|
/ |
/ |
/ |
/ |
/ |
/ |
--------<-原线段
- 反之直接不管
原线段
|_\ /|
/ |
/ |
/ |
/ |
/ |
/ |
--------<-现线段
与答案区间线段有交
- 画一个
\ /<-原线段
\ /
\ /
\ /
\ /
\/
/\
/ \
/ \
/ \
/ \
/ \<-现线段
- 鉴于msjing分讨锅太大了,所以msjing打算直接粘oiwiki的
粘的
- 具体来说,设当前区间的中点为 \(m\),我们拿新线段 \(f\) 在中点处的值与原最优线段 \(g\) 在中点处的值作比较
- 如果新线段 \(f\) 更优,则将 \(f\) 和 \(g\) 交换。那么现在考虑在中点处 \(f\) 不如 \(g\) 优的情况:
- 若在左端点处 \(f\) 更优,那么 \(f\) 和 \(g\) 必然在左半区间中产生了交点,\(f\) 只有在左区间才可能优于 \(g\),递归到左儿子中进行下传
- 若在右端点处 \(f\) 更优,那么 \(f\) 和 \(g\) 必然在右半区间中产生了交点,\(f\) 只有在右区间才可能优于 \(g\),递归到右儿子中进行下传
- 若在左右端点处 \(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\) 一个题解写的都比我好,不给了
后记
- 模拟退火太难了哦哦哦哦哦哦哦哦哦!

浙公网安备 33010602011771号