Gushing over P|

BigSmall_En

园龄:3年2个月粉丝:3关注:5

2022-08-25 16:15阅读: 31评论: 0推荐: 0

李超线段树

李超线段树

李超线段树可以求解以下问题:

  1. 插入一条形如 \(f(x)=kx+b\) 的直线或线段
  2. 查询在 \(x\) 位置的函数最值(最大或最小)。

李超线段树的写法很多,但是核心思想是一致的,以求解最大值为例讲解算法过程。

修改操作

既然是线段树,自然树上每个节点为一条线段 \([l,r]\),其中树上每个节点维护 \(mid=(l+r)/2\) 处的能取到最大值的函数的信息(即这个函数的 \(k\)\(b\))。

那么我们加入一条线段。有四种情况

  1. \([l,r]\) 的每个位置,新加入的线段均比原先线段(单指那条在 \(mid\) 处函数值最大的线段)劣,那么自然在 \(l,mid,r\) 这三个位置函数值均比原先线段小。
  2. \([l,r]\) 的每个位置,新加入的线段均比原先线段优,那么在 \(l,mid,r\) 处这三个位置函数值均比原先线段大,此时我们只需修改这个区间存储的函数转化为新加入的线段。为了复杂度正确,不用去修改 \([l,mid],[mid+1],r\) 这两个区间的信息(正确性可以在后续的查询操作中体现)。
  3. \([l,r]\)\(mid\) 位置,新加入的线段比原先线段优秀,修改这个区间存储的函数为新加入的函数。但是,如果原先的线段在 \(l\) 位置的值比新线段优秀,将原先的线段在 \([l,mid]\) 位置进行同样的修改操作(递归);如果在 \(r\) 位置的值比新线段优秀,就在 \([mid+1,r]\) 递归。
  4. \([l,r]\)\(mid\) 位置,新加入的线段没有原先线段优秀。如果新加入的线段在 \(l\) 位置比原有线段优秀,在 \([l,mid]\) 的节点进行修改;如果在 \(r\) 位置比原有线段优秀,在 \([mid+1,r]\) 的节点进行修改。

上述内容非常复杂,实际上很多内容本质是相同的,以下是算法实现

struct sgline{double k,b;}lin[N];int tot;
inline double hei(int id,int i){return lin[id].k*i+lin[id].b;}
void modify(int p,int l,int r,int id){//对线段完全覆盖的区间进行修改
	if(hei(id,mid)>hei(bel[p],mid))swap(bel[p],id);//这个写法真的挺强的
	if(hei(id,l)>hei(bel[p],l))modify(ls,l,mid,id);
	if(hei(id,r)>hei(bel[p],r))modify(rs,mid+1,r,id);
}
void update(int p,int l,int r,int L,int R,int id){
	if(L<=l&&r<=R){modify(p,l,r,id);return;}//id 这条线段完全覆盖此区间
	if(L<=mid)update(ls,l,mid,L,R,id);
	if(R>mid)update(rs,mid+1,r,L,R,id);
}

容易发现在 update 执行 modify 最多操作 \(\log n\) 次,而这些 modify 操作也最多递归 \(\log n\) 次,所以单次修改的复杂度是 \(O(\log^2 n)\) 的。

如果修改操作时全局修改的话,那么修改的复杂度时 \(O(\log n)\) 的。

查询操作

将线段树中每个包含位置 \(L\) 的节点存储的函数值计算 \(f(L)\) 并最值即可。这样保证了修改操作中第二种情况不递归的正确性。

ttfa query(int p,int l,int r,int L){
	ttfa tmp={hei(bel[p],L),bel[p]};
	if(l==r)return tmp;
	if(L<=mid)tmp=max(tmp,query(ls,l,mid,L));
	else tmp=max(tmp,query(rs,mid+1,r,L));
	return tmp;
}

询问复杂度 \(O(\log n)\)

P4097 [HEOI2013]Segment

真板子题

要求在平面直角坐标系下维护两个操作:

  1. 在平面上加入一条线段。记第 \(i\) 条被插入的线段的标号为 \(i\)
  2. 给定一个数 \(k\),询问与直线 \(x = k\) 相交的线段中,交点纵坐标最大的线段的编号。

强制在线。对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^5\)\(1 \leq y_0, y_1 \leq 10^9\)

题目加入的线段的方式是给出线段的两个端点,使用初中几何知识换算一下就可以了。

typedef pair<double,int>ttfa;
struct sgline{double k,b;}lin[N];int tot;
inline double hei(int id,int i){return lin[id].k*i+lin[id].b;}
inline void add(double x_1,double y_1,double x_2,double y_2){
	if(x_1==x_2)lin[++tot]={0,max(y_1,y_2)};
	else{lin[++tot].k=(y_2-y_1)/(x_2-x_1);lin[tot].b=y_1-lin[tot].k*x_1;}
}
int bel[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
void modify(int p,int l,int r,int id){//对线段完全覆盖的区间进行修改
	if(hei(id,mid)>hei(bel[p],mid))swap(bel[p],id);//这个写法真的挺强的
	if(hei(id,l)>hei(bel[p],l))modify(ls,l,mid,id);
	if(hei(id,r)>hei(bel[p],r))modify(rs,mid+1,r,id);
}
void update(int p,int l,int r,int L,int R,int id){
	if(L<=l&&r<=R){modify(p,l,r,id);return;}//id 这条线段完全覆盖此区间
	if(L<=mid)update(ls,l,mid,L,R,id);
	if(R>mid)update(rs,mid+1,r,L,R,id);
}
ttfa max(ttfa x,ttfa y){//纵坐标相同的情况下选择编号小的
	if(x.first==y.first)
		return x.second<y.second?x:y;
	return x.first>y.first?x:y;
}
ttfa query(int p,int l,int r,int L){
	ttfa tmp={hei(bel[p],L),bel[p]};
	if(l==r)return tmp;
	if(L<=mid)tmp=max(tmp,query(ls,l,mid,L));
	else tmp=max(tmp,query(rs,mid+1,r,L));
	return tmp;
}
int lasans=0;
inline int calc1(int x){return (x+lasans-1+39989)%39989+1;}
inline int calc2(int x){return (x+lasans-1+1000000000)%1000000000+1;}
int main(){
	int T=read();
	while(T--){
		int opt=read();
		if(opt==1){
			int x_1=calc1(read()),y_1=calc2(read());
			int x_2=calc1(read()),y_2=calc2(read());
			if(x_1>x_2)swap(x_1,x_2),swap(y_1,y_2);
			add(x_1,y_1,x_2,y_2);
			update(1,1,M,x_1,x_2,tot);
		}else{
			int L=calc1(read());
			printf("%d\n",lasans=query(1,1,M,L).second);
		}
	}
	return 0;
}

P4254 [JSOI2008]Blue Mary开公司

第一行 :一个整数 \(N\) ,表示方案和询问的总数。

接下来 \(N\) 行,每行开头一个单词QueryProject

若单词为Query,则后接一个整数 \(T\),表示 Blue Mary 询问第 \(T\) 天的最大收益。

若单词为Project,则后接两个实数 \(S\)\(P\),表示该种设计方案第一天的收益 \(S\),以及以后每天比上一天多出的收益 \(P\)

对于每一个Query,输出一个整数,表示询问的答案,并精确到整百元(以百元为单位,例如:该天最大收益为 210 或 290 时,均应该输出 2)。没有方案时回答询问要输出 0。

也是李超线段树的板子题,这里采用的是对 4 种情况直接分类讨论的写法。

同时注意这题是全局修改,不同在插入的时候不用判断线段区间 \([L,R]\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>

using namespace std;
const int N=1000005,M=500000;
struct lictree{
	struct sgline{double k,d;}lin[N];int tot;
	inline double hei(int id,int i){return lin[id].k*(i-1)+lin[id].d;}
	int bel[N<<2];
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	void update(int p,int l,int r,int id){
		if(hei(id,l)>=hei(bel[p],l)&&hei(bel[p],r)<=hei(id,r))
			{bel[p]=id;return;}
		if(hei(id,l)<=hei(bel[p],l)&&hei(bel[p],r)>=hei(id,r))
			return;
		if(lin[id].k>lin[bel[p]].k){
			if(hei(id,mid)>hei(bel[p],mid))//说明在mid 这个位置 id 的高度已经超过了所有线段,所以右区间和左区间的一部分的最高线段就是它
				update(ls,l,mid,bel[p]),bel[p]=id;//因为先前标记的直线有可能在左区间右贡献,所以这个节点的标记线段在左区间上修改
			else update(rs,mid+1,r,id);//否则在右区间继续找到这条线段超过所有线段的位置
		}else{
			if(hei(id,mid)>hei(bel[p],mid))
				update(rs,mid+1,r,bel[id]),bel[p]=id;
			else update(ls,l,mid,id);
		}
	}
	inline void insert(double k,double d){
		lin[++tot]={k,d};
		update(1,1,M,tot);
	}
	double queryhei(int p,int l,int r,int L){
		if(l==r)return hei(bel[p],L);
		double tmp=hei(bel[p],L);
		if(L<=mid)tmp=max(tmp,queryhei(ls,l,mid,L));
		else tmp=max(tmp,queryhei(rs,mid+1,r,L));
		return tmp;
	}
}t;
int main(){
	int T;scanf("%d",&T);
	char s[10];
	while(T--){
		scanf("%s",s);
		if(s[0]=='P'){
			double x,y;scanf("%lf%lf",&x,&y);
			t.insert(y,x);
		}else{
			int L;scanf("%d",&L);
			printf("%d\n",(int)floor(t.queryhei(1,1,M,L)/100));
		}
	}
	return 0;
}

例题

LG4655 [CEOI2017] Building Bridges

\(n\) 根柱子依次排列,每根柱子都有一个高度。第 \(i\) 根柱子的高度为 \(h_i\)

现在想要建造若干座桥,在第 \(i\) 根柱子和第 \(j\) 根柱子之间架桥需要 \((h_i-h_j)^2\) 的代价。

所有用不到的柱子都会被拆除。第 \(i\) 根柱子被拆除的代价为 \(w_i\),注意 \(w_i\) 不一定非负。

求通过桥梁把第 \(1\) 根柱子和第 \(n\) 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。

定义 \(dp_i\)\(1\sim i\) 均已经架桥的代价,\(s_i\)\(w_i\) 的前缀和。则 \(dp\) 转移非常好推。

\[\begin{align} dp_i&=\min_{j}^{i-1}\{dp_j+(h_i-h_j)^2+s_{i-1}-s_{j}\}\\ &=\min_{j}^{i-1}\{dp_j+h_i^2-2h_ih_j+h_j^2+s_{i-1}-s_j\} \end{align} \]

将定值从式子中分开,将 \(\min\) 中尝试写成一次函数的形式。

\[dp_i=h_i^2+s_{i-1}+\min\{h_jh_i+dp_j-s_j+h_j^2\} \]

发现对于 \(h_i\),求出 \(k=h_j,b=dp_j-s_j+h_j^2\)\(k\times h_i+b\) 最大即可。

那么这 \(i-1\) 个一次函数可以使用李超线段树维护。

综上,这题推式子还是比较基础的,可能难点就在李超树上了。

const int N=100005,M=1000006,LIM=1000000;
const ll INF=0x3f3f3f3f3f3f3f3f;
struct lictree{//维护最小值,全局修改
	struct sgline{ll k,b;}lin[N];int tot;
	inline ll hei(int id,ll i){return lin[id].k*i+lin[id].b;}
	int bel[M<<2];
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)
	void update(int p,int l,int r,int id){
		/*if(hei(id,l)<=hei(bel[p],l)&&hei(bel[p],r)>=hei(id,r))
			{bel[p]=id;return;}
		if(hei(id,l)>=hei(bel[p],l)&&hei(bel[p],r)<=hei(id,r))
			return;*/
		if(hei(id,mid)<hei(bel[p],mid))swap(bel[p],id);
		if(hei(id,l)<hei(bel[p],l))update(ls,l,mid,id);
		if(hei(id,r)<hei(bel[p],r))update(rs,mid+1,r,id);
	}
	ll query(int p,int l,int r,int L){
		ll tmp=hei(bel[p],L);
		if(l==r)return tmp;
		if(L<=mid)tmp=min(tmp,query(ls,l,mid,L));
		else tmp=min(tmp,query(rs,mid+1,r,L));
		return tmp;
	}
	inline void insert(ll k,ll b){
		lin[++tot]={k,b};
		update(1,1,LIM,tot);
	}
	inline void init(){lin[tot=0].b=INF;}//维护最小值所以初值最大
}t;
int n;ll w[N],h[N],s[N],dp[N];
int main(){
	n=read();t.init();//写了init没有执行,呜呜呜
	for(int i=1;i<=n;++i)h[i]=read();
	for(int i=1;i<=n;++i){
		w[i]=read();
		s[i]=s[i-1]+w[i];
	}
	dp[1]=0;t.insert(-2ll*h[1],-s[1]+dp[1]+h[1]*h[1]);
	for(int i=2;i<=n;++i){
		dp[i]=t.query(1,1,LIM,h[i])+s[i-1]+h[i]*h[i];
		t.insert(-2ll*h[i],-s[i]+dp[i]+h[i]*h[i]);
	}
	printf("%lld\n",dp[n]);
	return 0;
}
//	 343ms /  6.77MB /  1.62KB C++14 (GCC 9) O2

CF1715E Long Way Home

\(n\) 座城市,城市间有 \(m\) 条双向道路,通过第 \(i\) 条道路需要花费 \(w_i\) 的时间,任意两个城市之间都有航班,乘坐城市 \(u\)\(v\) 之间的航班需要花费 \((u-v)^2\) 的时间。
现在请对于任意城市 \(i(1 \le i \le n)\),求出从城市 \(1\) 出发,到达城市 \(i\) 所需要的最短时间,注意从城市 \(1\)\(i\) 的过程中最多乘坐 \(k\) 次航班\(k\leq 20\)

看上去有点像分层图,但是 \((u-v)^2\) 的花费和 LG4655 可以说是非常相似,可以考虑使用斜率优化。

行走的过程一定是这样的:走道路(其实可以不走)、坐飞机、走道路、坐飞机、……、坐飞机、走道路。所以我们可以跑 \(k+1\) 遍最短路,并在这 \(k\) 遍最短路中跑 \(k\) 遍斜率优化。

转移是显而易见的

\[dp_u\gets dp_v+(u-v)^2 \]

转化成一次函数的形式

\[\begin{aligned} dp_u&\gets dp_v+u^2-2uv+v^2\\ dp_u&\gets (2uv+dp_v+v^2)+u^2 \end{aligned} \]

所以我们将函数 \(f(x)=2vx+dp_v+v^2\) 存储在李超线段树中,查询 \(x\) 的最小函数值加上 \(x^2\) 即可。code

CF1303G Sum of Prefix Sums

有一颗 \(n\) 个节点的树 \((2 \leq n \leq 150000)\)

树每个节点有一个权值 \(a_i (1 \leq a_i \leq 10^6)\)

定义树上\(u \rightarrow v\)的链的权值如下:

  • \(u\)\(v\)的路径上点的权值依次排列在数组中
  • 该数组的前缀和的和即这条路径的权值

请求出权值最大的链,输出权值

李超树的经典题目了。

实质上一个路径为 \(x_1,x_2,\dots x_k\) 的话,这条路径的权值为 \(\sum_{i=1}^{k}i\times a_{x_i}\)

既然是求路径的问题,那么往点分治方向思考。

那么对于路径 \(x_1\to x_k\),选择一个中间点 \(x_m\),可以拆分成两个子路径。

  • 第一部分是 \(x_1\to x_m\),设 \(l_1=m,v_1=\sum_{i=1}^{m}i\times a_{x_i}\)
  • 第二部分是 \(x_{m+1}\to x_k\),设 \(s_2=\sum_{i=m+1}^{k}a_i,v_2=\sum_{i=m+1}^{k}(i-m)a_{x_i}\)

那么整条路径的权值是 \(v_1+l_1s_2+v_2\)

可以将 \(f(x)=s_2x+v_2\) 看作已经插入的一条直线,那么查询 \(f(l_1)+v_1\) 即可得到新加入的点到中转点与之前插入的路径所形成路径的权值。

那么我们可以使用点分治,将分治点的一棵子树的所有 \(s_2,v_2\) 插入李超树中,遍历另一颗子树的时候查询 \(f(l_1)+v_1\) 即可。

至于如何求 \(l,s,v1,v_2\) 这四个信息,可以在点分治的过程中求得,一些细节具体见代码。点分治时间复杂度 \(O(n\log n)\),李超树单次复杂度 \(O(\log n)\),重构李超树复杂度为树的深度,分治得到的深度和不超过 \(O(n\log n)\)。总时间复杂度 \(O(n\log ^2 n)\)

struct lictree{
	struct sgline{ll k,b;}lin[N];int tot;
	inline ll hei(int id,int i){return lin[id].k*i+lin[id].b;}
	int bel[N<<2],len;
	#define ls p<<1
	#define rs p<<1|1
	#define mid ((l+r)>>1)

	void build(int p,int l,int r){
		bel[p]=0;
		if(l==r)return;
		build(ls,l,mid);
		build(rs,mid+1,r);
	}
	void update(int p,int l,int r,int id){//注意是全局修改
		if(hei(id,l)>=hei(bel[p],l)&&hei(id,r)>=hei(bel[p],r))
			{bel[p]=id;return;}
		if(hei(id,l)<=hei(bel[p],l)&&hei(id,r)<=hei(bel[p],r))
			return;
		if(hei(id,mid)>hei(bel[p],mid))swap(bel[p],id);
		if(hei(id,l)>hei(bel[p],l))update(ls,l,mid,id);
		if(hei(id,r)>hei(bel[p],r))update(rs,mid+1,r,id);
	}
	ll query(int p,int l,int r,int L){
		ll tmp=hei(bel[p],L);
		if(l==r)return tmp;
		if(L<=mid)tmp=max(tmp,query(ls,l,mid,L));
		else tmp=max(tmp,query(rs,mid+1,r,L));
		return tmp;
	}

	inline void init(int d){tot=0,len=d;build(1,1,len);}
	inline void insert(ll k,ll b){
		lin[++tot]={k,b};
		update(1,1,len,tot);
	}

	#undef ls
	#undef rs
	#undef mid
}t;
int n;ll a[N],ans;
vector<int>edge[N];
int siz[N],fiz[N],root,all;bool vis[N];
int dep[N],mxd,col[N],now,stk[N],top;ll v1[N],v2[N],s[N],l[N];
void findroot(int u,int f){
	siz[u]=1,fiz[u]=0;
	for(auto v:edge[u]){
		if(v==f||vis[v])continue;
		findroot(v,u);
		siz[u]+=siz[v];
		fiz[u]=max(fiz[u],siz[v]);
	}fiz[u]=max(fiz[u],all-siz[u]);
	if(fiz[u]<fiz[root])root=u;
}
void getlis(int u,int f,ll dis1,ll dis2,ll sum){//此处的sum是记录了根节点的
	dep[u]=dep[f]+1;
	mxd=max(mxd,dep[u]);
	bool flag=0;
	for(auto v:edge[u]){
		if(v==f||vis[v])continue;
		flag=1;
		getlis(v,u,dis1+sum+a[v],dis2+a[v]*dep[u],sum+a[v]);
	}
	if(!flag){//答案一定是由叶子节点组成
		stk[++top]=u;col[top]=now;
		v1[top]=dis1,v2[top]=dis2,s[top]=sum-a[root],l[top]=dep[u];
	}
}
void solve(int u){
	vis[u]=1;top=0;
	dep[u]=mxd=1;
	for(auto v:edge[u]){
		if(!vis[v]){
			now=v;
			getlis(v,u,a[v]+2*a[u],a[v],a[u]+a[v]);
		}
	}
	stk[++top]=u;col[top]=0;
	v1[top]=a[u],l[top]=1,v2[top]=s[top]=0;
	t.init(mxd);
	col[top+1]=col[0]=-1;
	for(int i=1,j=i;i<=top;i=j){
		while(col[j]==col[i])
			{ans=max(ans,t.query(1,1,mxd,l[j])+v1[j]);++j;}
		j=i;
		while(col[j]==col[i])
			{t.insert(s[j],v2[j]);++j;}
	}
	t.init(mxd);
	for(int i=top,j=i;i>=1;i=j){//同时路径的答案是有方向的,所以反着做一次
		while(col[j]==col[i])
			{ans=max(ans,t.query(1,1,mxd,l[j])+v1[j]);--j;}
		j=i;
		while(col[j]==col[i])
			{t.insert(s[j],v2[j]);--j;}
	}
	for(auto v:edge[u]){
		if(vis[v])continue;
		all=siz[v],root=0;
		findroot(v,u);
		solve(root);
	}
}
int main(){
	n=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	for(int i=1;i<=n;++i)a[i]=read();
	fiz[root=0]=INF,all=n;
	findroot(1,0);
	solve(root);
	printf("%lld\n",ans);
	return 0;
}

CF932F Escape Through Leaf

有一颗 \(n\) 个节点的树(节点从 \(1\)\(n\) 依次编号)。每个节点有两个权值,第 \(i\) 个节点的权值为 \(a_i,b_i\)

你可以从一个节点跳到它的子树内任意一个节点上。从节点 \(x\) 跳到节点 \(y\) 一次的花费为 \(a_x\times b_y\)。跳跃多次走过一条路径的总费用为每次跳跃的费用之和。请分别计算出每个节点到达树的每个叶子节点的费用中的最小值。

注意:就算树的深度为 \(1\),根节点也不算做叶子节点。另外,不能从一个节点跳到它自己.

\(2\leq n\leq 10^5\)\(-10^5\leq a_i\leq 10^5\)\(-10^5\leq b_i\leq 10^5\)

可以考虑DP,假设 \(dp_u\) 为从 \(u\) 出发到叶子节点的最小代价。假设 \(v\)\(u\) 子树中的点,则:

\[dp_u\gets dp_v+a_u\times b_v \]

那么我们可以将 \(f(x)=b_vx+dp_v\) 加入李超线段树中,查询 \(f(a_u)\) 的最小值即可得到 \(dp_u\)。但是问题是这是个树上问题。那么我们可以使用线段树合并。

李超线段树合并时 \(O(n\log n)\) 的。核心点在于一个点只会保存一条直线的信息,每条直线的信息最多在一个点被记录。而合并节点的时候每条直线信息的存储点的深度只会变大或不变,那么深度最大变大 \(\log n\) 次,一共 \(n\) 条直线。总时间复杂度 \(O(n\log n)\)

注意这题中 \(a_u\) 可能为负数,下标整体平移为正就行了。

inline ll read(){
	ll x=0,f=1;char ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=222222;
const ll BAS=100005,LIM=BAS*2;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,cnt;
ll a[N],b[N],dp[N];
vector<int>edge[N];
struct sgline{ll k,b;}lin[N];
inline ll hei(int id,ll i){return lin[id].k*i+lin[id].b;}
int lc[N<<5],rc[N<<5],bel[N<<5],root[N],tot;
#define mid ((l+r)>>1)
void update(int &p,int l,int r,int id){
	if(!p){p=++tot;bel[p]=id;return;}
	if(hei(id,mid)<hei(bel[p],mid))swap(bel[p],id);
	if(hei(id,l)<hei(bel[p],l))update(lc[p],l,mid,id);
	if(hei(id,r)<hei(bel[p],r))update(rc[p],mid+1,r,id);
}
ll query(int p,int l,int r,ll L){
	if(!p)return INF;
	ll tmp=hei(bel[p],L);
	if(L<=mid)tmp=min(tmp,query(lc[p],l,mid,L));
	else tmp=min(tmp,query(rc[p],mid+1,r,L));//这里写成max了,尴尬
	return tmp;
}
int merge(int x,int y,int l,int r){
	if(!x||!y)return x+y;
	update(x,l,r,bel[y]);
	lc[x]=merge(lc[x],lc[y],l,mid);
	rc[x]=merge(rc[x],rc[y],mid+1,r);
	return x;
}
void dfs(int u,int f){
	for(auto v:edge[u]){
		if(v==f)continue;
		dfs(v,u);
		root[u]=merge(root[u],root[v],1,LIM);
	}
	dp[u]=query(root[u],1,LIM,a[u]+BAS);
	if(dp[u]==INF)dp[u]=0;
	lin[u]={b[u],dp[u]-b[u]*BAS};//b[u]*BAS 因为上面右移多算的贡献
	update(root[u],1,LIM,u);
}

int main(){
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	for(int i=1;i<=n;++i)b[i]=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;++i)
		printf("%lld ",dp[i]);
	putchar('\n');
	return 0;
}

本文作者:BigSmall_En

本文链接:https://www.cnblogs.com/BigSmall-En/p/16624627.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   BigSmall_En  阅读(31)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起