李超线段树

李超线段树

李超线段树

发现要维护的问题十分难做,所以我们要引入李超线段树。

我们发现,如果在一个区间内,一条线段的整体在另一条线段上方,那么这条线段一定更优,我们称之为最优线段。但是如果并不是这样,应该如何做呢?

这里给出线段为一个区间内的最优线段的条件:

  • 线段的定义域覆盖了整个区间。

  • 线段在区间中点的位置最高。

现在我们假设 \(l_1\) 为区间 \([l,r]\) 内的最优线段,我们要插入一条新的线段 \(l_2\)

如果当前区间没有最优线段,或者 \(l_2\) 整体在 \(l_1\) 上方,那么可以直接用 \(l_2\) 替换 \(l_1\) 作为该区间的最优线段。

而如果 \(l_1\) 整体在 \(l_2\) 上方,那么显然 \(l_2\) 是劣的,不用再进行修改及递归。

最后是比较难的一种情况:如果两个线段谁也没有被谁完全压住,那么他们一定出现交点。

下面记区间中点为 \(mid\)

我们比较两条线段在 \(mid\) 处的高度,更高的那条作为最优线段。但是,虽然剩下那一条线段在这个区间不是最优的,但是可能在子区间内最优,所以我们要进行递归,用更劣的那条线段更新子区间。

我们记 \(l_3\) 为更新子区间的线段(其实就是上面两条线段更劣的那一个)。我们对交点的位置进行分类讨论:

  • 交点在 \(mid\) 左侧:画个图可以发现 \(l_3\) 只可能在左子区间成为最优线段,故递归左子区间。

  • 交点在 \(mid\) 右侧:同理,递归右子区间。

  • 交点在 \(mid\) 处:看 \(l_3\)\(l\) 还是 \(r\) 的位置更高,就选择哪一边递归。

我们的基本思想就说完了,下面说一下作者在写这道题的时候遇见的错误:

  • 把所有模数都看成 \(39989\)

  • 没有看到交点相同时要选择编号最小的线段。

既然是板子题,下面就放一下代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define mod1 39989
#define mod2 1000000000
#define eps 1e-8
using namespace std;
int cnt,res,tot,rt;
struct line{
	double k,b;
}a[N];
struct node{
	int ls,rs,id;
}w[N<<2];
bool check(int i,int j,int x){
	if(a[i].k*x+a[i].b-a[j].k*x-a[j].b>eps)return 1;
	if(a[j].k*x+a[j].b-a[i].k*x-a[i].b>eps)return 0;
	return i<j;//函数用于判断哪个线段更优 
}
void modify(int &u,int l,int r,int L,int R,int now){
	if(l>R||r<L)return;//完全无交 
	if(!u)u=++cnt;//动态开点 
	if(l>=L&&r<=R){//完全包含 
		if(check(now,w[u].id,l)&&check(now,w[u].id,r)){//新的线段更优,直接更新 
			w[u].id=now;
			return;
		}
		if(check(w[u].id,now,l)&&check(w[u].id,now,r)){//旧的线段更优,直接跳过 
			return;
		}
		int mid=l+r>>1;
		if(check(now,w[u].id,mid)){
			swap(now,w[u].id);//区间内最优线段 
		}
		if(check(now,w[u].id,l)){//用更劣的线段更新子区间 
			modify(w[u].ls,l,mid,L,R,now);
		}
		if(check(now,w[u].id,r)){
			modify(w[u].rs,mid+1,r,L,R,now);
		}
	}
	else{
		int mid=l+r>>1;
		modify(w[u].ls,l,mid,L,R,now);//不被完全包含,递归解决 
		modify(w[u].rs,mid+1,r,L,R,now);
	}
}
void solve(int u,int l,int r,int p){
	if(!u||r<p||l>p)return;//没有点或者完全无交 
	if(check(w[u].id,res,p)){//当前答案比之前的答案更优 
		res=w[u].id;
	}
	if(l==r)return;
	int mid=l+r>>1;
	solve(w[u].ls,l,mid,p);//递归解决 
	solve(w[u].rs,mid+1,r,p);
}
signed main(){
	int n,las=0;
	cin>>n;
	while(n--) {
		int op,x_0,y_0,x_1,y_1;
		cin>>op;
		if(op==0){
			int k;
			cin>>k;
			res=0;
			k=(k+las-1)%mod1+1;
			solve(1,1,mod1,k);//查询答案 
			las=res;
			cout<<res<<'\n';
		}
		else{
			cin>>x_0>>y_0>>x_1>>y_1;
			x_0=(x_0+las-1)%mod1+1;
			y_0=(y_0+las-1)%mod2+1;
			x_1=(x_1+las-1)%mod1+1;
			y_1=(y_1+las-1)%mod2+1;
			if(x_0>x_1)swap(x_0,x_1),swap(y_0,y_1);//发现反了要换回来 
			if(x_0==x_1)a[++tot]={0,max(y_0,y_1)*1.0};
			else a[++tot].k=1.0*(y_1-y_0)/(x_1-x_0),a[tot].b=y_0-x_0*a[tot].k;//求斜率和截距 
			modify(rt,1,mod1,x_0,x_1,tot);//修改 
		}
	}
	return 0;
}

Blue Mary 开公司

和上一道题差不多,本质都是维护一堆线段然后求一个 \(x\) 坐标的最高位置。

所以我们还是直接使用模板。不过需要注意每个位置的截距要减去斜率才是真正的斜率,作者就因为这个挂了很多发。

既然这么简单,就直接放一下代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define mod1 100000
#define eps 1e-8
using namespace std;
int cnt,res,tot,rt,all;
struct line{
	double k,b;
}a[N];
struct node{
	int ls,rs,id;
}w[N<<2];
bool check(int i,int j,int x){
	if(a[i].k*x+a[i].b-a[j].k*x-a[j].b>eps)return 1;
	if(a[j].k*x+a[j].b-a[i].k*x-a[i].b>eps)return 0;
	return i<j;
}
void modify(int &u,int l,int r,int L,int R,int now){
	if(l>R||r<L)return;
	if(!u)u=++cnt;
	if(l>=L&&r<=R){
		if(check(now,w[u].id,l)&&check(now,w[u].id,r)){
			w[u].id=now;
			return;
		}
		if(check(w[u].id,now,l)&&check(w[u].id,now,r)){
			return;
		}
		int mid=l+r>>1;
		if(check(now,w[u].id,mid)){
			swap(now,w[u].id);
		}
		if(check(now,w[u].id,l)){ 
			modify(w[u].ls,l,mid,L,R,now);
		}
		if(check(now,w[u].id,r)){
			modify(w[u].rs,mid+1,r,L,R,now);
		}
	}
	else{
		int mid=l+r>>1;
		modify(w[u].ls,l,mid,L,R,now); 
		modify(w[u].rs,mid+1,r,L,R,now);
	}
}
void solve(int u,int l,int r,int p){
	if(!u||r<p||l>p)return;
	if(check(w[u].id,res,p)){
		res=w[u].id;
	}
	if(l==r)return;
	int mid=l+r>>1;
	solve(w[u].ls,l,mid,p);
	solve(w[u].rs,mid+1,r,p);
}
signed main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		string op;
		double x,y;
		cin>>op;
		if(op[0]=='P'){
			cin>>x>>y;
			a[++tot].k=y,a[tot].b=x-y;
			modify(rt,1,mod1,1,mod1,tot);
		}
		else{
			all++;
			int k;
			res=0;
			cin>>k;
			solve(1,1,mod1,k);
			cout<<(int)((a[res].k*k+a[res].b)/100)<<'\n';
		}
	}
	return 0;
}

游戏

我们先设 \(s,t\)\(lca\)\(x\),一个点 \(u\) 到根的距离为 \(dis_u\)

于是进行套路地拆路径,把路径拆成 \(s\rightarrow x\)\(t\rightarrow x\) 两条链。

我们接下来对每一种路径讨论该路径中点加上的值:

  • \(s\rightarrow x\):每个点 \(u\) 加上的值为 \(a\times (dis_s-dis_u)+b\),然后展开为 \(-a\times dis_u+a\times dis_s+b\)

可以发现,把这个式子看作一条线段,斜率为 \(-a\),截距为 \(a\times dis_s+b\)

  • \(t\rightarrow x\):每个点 \(u\) 加上的值为 \(a\times (dis_s-dis_x+dis_u-dis_x)+b\),然后和上面一样进行展开,展开结果为 \(a\times dis_u+a\times (dis_s-2\times dis_x)+b\)

同理,这是一条斜率为 \(a\),截距为 \(a\times (dis_s-2\times dis_x)+b\) 的线段。

于是我们套路地使用李超线段树进行维护线段,而拆路径可以让我们想到树链剖分,所以我们使用这两个东西维护即可。

所以这道题就基本转化为了模板题,只不过是维护每个横坐标的线段对应的纵坐标最小值,下面就放一下代码:

#include<bits/stdc++.h>
#define int long long
#define N 100005
#define M 200005
#define inf 123456789123456789
using namespace std;
int h[N],e[M],w[M],ne[M],idx;
void add(int a,int b,int c){
	e[idx]=b;w[idx]=c;ne[idx]=h[a];h[a]=idx++;
}
int n,m,dfn[N],id[N],dep[N],top[N];
int fa[N],siz[N],son[N],dis[N],ts;
void dfs1(int u,int f){
	if(f!=-1)dep[u]=dep[f]+1;
	fa[u]=f;
	siz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u])continue;
		dis[j]=dis[u]+w[i];
		dfs1(j,u);
		siz[u]+=siz[j];
		if(!son[u]||siz[son[u]]<siz[j])son[u]=j;
	}
}
void dfs2(int u,int f){
	top[u]=f;
	dfn[u]=++ts;
	id[ts]=u;
	if(son[u])dfs2(son[u],f);
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u]||j==son[u])continue;
		dfs2(j,j);
	}
}
int get_lca(int a,int b){
	while(top[a]!=top[b]){
		if(dep[top[a]]<dep[top[b]])swap(a,b);
		a=fa[top[a]];
	}
	return dep[a]<dep[b]?a:b;
}
struct line{
	int k,b;
	int get_y(int x){
		return k*dis[id[x]]+b;
	}
};
struct node{
	int l,r,mi;
	line bes;
}tr[N<<2];
void pushup1(int u){
	tr[u].mi=min({tr[u].mi,tr[u<<1].mi,tr[u<<1|1].mi});
}
void pushup2(int u,int l,int r){
	tr[u].mi=min({tr[u].mi,tr[u].bes.get_y(l),tr[u].bes.get_y(r)});
}
void build(int u,int l,int r){
	tr[u]={l,r,inf,{0,inf}};
	if(l==r)return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup1(u);
}
void modify(int u,int L,int R,line now){
	int l=tr[u].l,r=tr[u].r;
	if(l>=L&&r<=R){
		int lp=tr[u].bes.get_y(l);
		int rp=tr[u].bes.get_y(r);
		int lq=now.get_y(l);
		int rq=now.get_y(r);
		if(lp>=lq&&rp>=rq){
			tr[u].bes=now;
			pushup2(u,l,r);
		}
		else if(lp>=lq||rp>=rq){
			int mid=l+r>>1;
			int mp=tr[u].bes.get_y(mid);
			int mq=now.get_y(mid);
			if(mp>mq)swap(tr[u].bes,now),swap(lp,lq);
			if(lp>lq)modify(u<<1,L,R,now);
			else modify(u<<1|1,L,R,now);
			pushup2(u,l,r);
			pushup1(u);
		}
	}
	else{
		int mid=l+r>>1;
		if(L<=mid)modify(u<<1,L,R,now);
		if(R>mid)modify(u<<1|1,L,R,now);
		pushup1(u);
	}
}
int qry(int u,int L,int R){
	int l=tr[u].l,r=tr[u].r;
	if(l>=L&&r<=R)return tr[u].mi;
	int lx=max(l,L),rn=min(r,R);
	int res=min(tr[u].bes.get_y(lx),tr[u].bes.get_y(rn));
	int mid=l+r>>1;
	if(L<=mid)res=min(res,qry(u<<1,L,R));
	if(R>mid)res=min(res,qry(u<<1|1,L,R));
	return res;
}
void modify_path(int a,int b,line now){
	while(top[a]!=top[b]){
		modify(1,dfn[top[a]],dfn[a],now);
		a=fa[top[a]];
	}
	modify(1,dfn[b],dfn[a],now);
}
int qry_path(int a,int b){
	int res=inf;
	while(top[a]!=top[b]){
		res=min(res,qry(1,dfn[top[a]],dfn[a]));
		a=fa[top[a]];
	}
	res=min(res,qry(1,dfn[b],dfn[a]));
	return res;
}
signed main(){
	cin>>n>>m;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++){
		int a,b,c;
		cin>>a>>b>>c;
		add(a,b,c);add(b,a,c);
	}
	dfs1(1,-1);
	dfs2(1,1);
	build(1,1,n);
	while(m--){
		int op,s,t,a,b;
		cin>>op>>s>>t;
		int lca=get_lca(s,t);
		if(op==1){
			cin>>a>>b;
			modify_path(s,lca,{-a,a*dis[s]+b});
			modify_path(t,lca,{a,a*(dis[s]-2*dis[lca])+b});
		}
		else{
			int res1=qry_path(s,lca);
			int res2=qry_path(t,lca);
			cout<<min(res1,res2)<<'\n';	
		}
	}
	return 0;
}
posted @ 2024-07-13 13:30  zxh923  阅读(21)  评论(0编辑  收藏  举报