李超树学习笔记
1 [HEOI2013]Segment
每个点有一个标记,标记是一条线段。
对于一次线段的插入,考虑当前的线段树区间 [l,r],假如它完全包含于定义域 [L,R],则考虑比较 [l,r] 的标记线段与插入线段的关系,如下图:
交点要么 \(\in [l,mid]\),要么 \(\notin[l,mid]\),因而 \([l,mid]\) 和 \([mid+1,r]\) 中必有之一的最大函数值由单一线段构成,将插入线段赋值给当前区间的标记线段,用插入线段递归更新另一个半区间,注意此时可能需要 swap 插入线段和原大区间标记线段,取决于另一区间是那条线段始终最大。
利用标记永久化的思想,可以知道所查询的答案等于查到叶子经过的 log n 个节点的标记对应的 f(x) 取 max。
#include <bits/stdc++.h>
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=40005;
const double eps=1e-9;
int n=40000,q,las,tot;
struct Line {double k,b;int id;}t[N<<2];
void upd(Line w,int l,int r,int k){
if(l==r){
if(t[k].k*l+t[k].b<w.k*l+w.b)t[k]=w;
return;
}
int mid=l+r>>1;
if(abs(t[k].k-w.k)<eps){if(t[k].b<w.b)t[k]=w;return;}
if(1.0*(w.b-t[k].b)/(t[k].k-w.k)<=mid){
if(t[k].k*(mid+1)+t[k].b<w.k*(mid+1)+w.b)swap(t[k],w);
upd(w,l,mid,k<<1);
}
else {
if(t[k].k*mid+t[k].b<w.k*mid+w.b)swap(t[k],w);
upd(w,mid+1,r,k<<1|1);
}
}
void chg(int L,int R,Line w,int l,int r,int k){
//printf("")
if(L<=l&&r<=R){
upd(w,l,r,k);
return;
}
int mid=l+r>>1;
if(L<=mid)chg(L,R,w,l,mid,k<<1);
if(R>mid)chg(L,R,w,mid+1,r,k<<1|1);
}
double Y;int id;
void ask(int p,int l,int r,int k){
//printf("[%.2f %.2f]",t[k].k,t[k].b);
if(Y<t[k].k*p+t[k].b)Y=t[k].k*p+t[k].b,id=t[k].id;
else if(abs(Y-t[k].k*p-t[k].b)<eps)id=min(id,t[k].id);
if(l==r)return;
int mid=l+r>>1;
if(p<=mid)ask(p,l,mid,k<<1);
else ask(p,mid+1,r,k<<1|1);
}
int main(){
q=read();
for(int op,x,y,u,v;q--;){
op=read();
if(!op){
x=(read()+las-1)%39989+1;
Y=-1e9,id=0,ask(x,1,n,1),las=id;
cout<<las<<'\n';
}
else {
x=(read()+las-1)%39989+1;
y=(read()+las-1)%1000000000+1;
u=(read()+las-1)%39989+1;
v=(read()+las-1)%1000000000+1;
if(x>u)swap(x,u),swap(y,v);
if(x==u)chg(x,x,Line{(double)0,(double)max(y,v),++tot},1,n,1);
else chg(x,u,Line{1.0*(y-v)/(x-u),y-1.0*(y-v)/(x-u)*x,++tot},1,n,1);
}
}
}
2 党²
一个集合 P 有 n 个 (li,ri,vi) 元素,一个集合 Q 有 n 个 (ai,bi,ci) 元素,从 P 中选择一个元素,Q 中选择一个元素,使得两个元素的 max(0,min(r,b)-max(l,a)+1)*c*v 最大,输出最大值。
n ≤ 100000, l ≤ r, a ≤ b, 0 ≤ l,r,v,a,b,c ≤ 100000
首先分类讨论,对于 [l,r]⊆[a,b] 的情况,明显可以按右端点排序并在扫描一遍的过程中维护左端点为下标的线段树,得到答案。反之亦然。
对于 l<a 且 r ≤ b 的情况,同样是把 P、Q 按右端点排序,从左往右枚举 (a,b,c),并把所有 r ≤ b 的 (l,r,v) 继续加入,那么此时原式可以化简为 (r-a+1)*c*v,所以只需要考察 (r-a+1)*c 的最大值;该式子站在 (l,r,v) 的角度可以看做 f(x)=(r-x+1)*c 这个一次函数在 x=a 处的取值,因而变成李超树模板题。
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
register char ch=getchar();register int x=0;
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=1e5+5;
const double eps=1e-9;
int n,ans,s[N<<2];
struct node1 {int l,r,v;}a[N];
struct node2 {int a,b,c;}b[N];
void chg2(int p,int v,int l=0,int r=1e5,int k=1){
if(l==r){s[k]=max(s[k],v);return;}
int mid=l+r>>1;
if(p<=mid)chg2(p,v,l,mid,k<<1);
else chg2(p,v,mid+1,r,k<<1|1);
s[k]=max(s[k<<1],s[k<<1|1]);
}
int ask2(int L,int R,int l=0,int r=1e5,int k=1){
if(L<=l&&r<=R)return s[k];
int mid=l+r>>1,mx=0;
if(L<=mid)mx=max(mx,ask2(L,R,l,mid,k<<1));
if(R>mid)mx=max(mx,ask2(L,R,mid+1,r,k<<1|1));
return mx;
}
struct Line {double k,b;}t[N<<2];
void upd(Line w,int l,int r,int k){
if(l==r){
if(t[k].k*l+t[k].b<w.k*l+w.b)t[k]=w;
return;
}
int mid=l+r>>1;
if(abs(t[k].k-w.k)<eps){if(t[k].b<w.b)t[k]=w;return;}
if(1.0*(w.b-t[k].b)/(t[k].k-w.k)<=mid){
if(t[k].k*(mid+1)+t[k].b<w.k*(mid+1)+w.b)swap(t[k],w);
upd(w,l,mid,k<<1);
}
else {
if(t[k].k*mid+t[k].b<w.k*mid+w.b)swap(t[k],w);
upd(w,mid+1,r,k<<1|1);
}
}
void chg(int L,int R,Line w,int l=0,int r=1e5,int k=1){
if(L<=l&&r<=R){
upd(w,l,r,k);
return;
}
int mid=l+r>>1;
if(L<=mid)chg(L,R,w,l,mid,k<<1);
if(R>mid)chg(L,R,w,mid+1,r,k<<1|1);
}
double Y;
void ask(int p,int l=0,int r=1e5,int k=1){
Y=max(Y,t[k].k*p+t[k].b);
if(l==r)return;
int mid=l+r>>1;
if(p<=mid)ask(p,l,mid,k<<1);
else ask(p,mid+1,r,k<<1|1);
}
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i].l=read(),a[i].r=read(),a[i].v=read();
for(int i=1;i<=n;i++)b[i].a=read(),b[i].b=read(),b[i].c=read();
sort(a+1,a+n+1,[](node1 a,node1 b){return a.r<b.r;});
sort(b+1,b+n+1,[](node2 a,node2 b){return a.b<b.b;});
for(int i=1,j=1;i<=n;i++){
while(j<=n&&a[j].r<=b[i].b)chg2(a[j].l,(a[j].r-a[j].l+1)*a[j].v),j++;
ans=max(ans,ask2(b[i].a,b[i].b)*b[i].c);
}
memset(s,0,sizeof s);
for(int i=1,j=1;i<=n;i++){
while(j<=n&&b[j].b<=a[i].r)chg2(b[j].a,(b[j].b-b[j].a+1)*b[j].c),j++;
ans=max(ans,ask2(a[i].l,a[i].r)*a[i].v);
}
for(int i=1,j=1;i<=n;i++){
while(j<=n&&a[j].r<=b[i].b)chg(a[j].l,a[j].r,Line{-(double)a[j].v,(double)a[j].v*(a[j].r+1)}),j++;
ans=max(ans,(Y=-1e9,ask(b[i].a),(int)(Y+0.5))*b[i].c);
}
for(int i=1;i<=4e5;i++)t[i].k=t[i].b=0;
for(int i=1,j=1;i<=n;i++){
while(j<=n&&b[j].b<=a[i].r)chg(b[j].a,b[j].b,Line{-(double)b[j].c,(double)b[j].c*(b[j].b+1)}),j++;
ans=max(ans,(Y=-1e9,ask(a[i].l),(int)(Y+0.5))*a[i].v);
}
cout<<ans;
}