草庐IT

「学习笔记」树链剖分

Keven-He 2023-03-28 原文

「学习笔记」树链剖分

点击查看目录

树链剖分

树链剖分就是把一棵树分成多个链,再维护每条链的信息。剖分的方法有很多种,如重链剖分,长链剖分。一般情况下,树链剖分指重链剖分。

算法

By OI-Wiki:

定义重子节点表示其子节点中子树最大的子结点。如果有多个子树最大的子结点,取其一。如果没有子节点,就无重子节点。

定义轻子节点表示剩余的所有子结点。

从这个结点到重子节点的边为重边

到其他轻子节点的边为轻边

若干条首尾衔接的重边构成重链

把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。

如图:

为什么我要整段搬过来?因为除了上边那点东西本来就没什么可写的。

实现

首先我们要预处理出以下数组:

  • fa[u]:节点 \(u\) 的父亲。

  • dep[u]:节点 \(u\) 的深度。

  • sz[u]:以节点 \(u\) 为根的子树大小。

  • son[u]:节点 \(u\) 的重儿子。

  • top[u]:节点 \(u\) 所在链的链顶。

  • dfn[u]:节点 \(u\)\(\operatorname{dfs}\) 序。

  • rk[u]\(\operatorname{dfs}\) 序为 \(u\) 的节点。

这些东西并不难求,下面直接给出代码:

void Dfs1(ll u){
	son[u]=-1,sz[u]=1;
	far(v,tu[u]){
		if(v==fa[u])continue;
		dep[v]=dep[u]+1,fa[v]=u;
		Dfs1(v);
		sz[u]+=sz[v];
		if(son[u]==-1||sz[v]>sz[son[u]])son[u]=v;//取重儿子
	}
	return;
}
void Dfs2(ll u,ll t){
	top[u]=t;
	dfn[u]=++cnt;
	rk[cnt]=u;
	if(son[u]==-1)return;
	Dfs2(son[u],t);//优先遍历重儿子,因为这样可以保证一条链上的 dfs 序连续,方便数据结构处理!!!
	far(v,tu[u])if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
	return;
}

接下来问题是:如何更改和查询?

我们让输入的两个节点 \(u,v\) 不断往上跳,具体来说:

  • 如果 \(u,v\) 在同一条链上,直接查询/修改两点的 \(\operatorname{dfs}\) 序之间的值。

  • 如果 \(u,v\) 不在同一条链上,直接查询/修改 深度较深的节点 与 它所在链的链顶的节点 的 \(\operatorname{dfs}\) 序之间的值,并将其跳到它所在链的链顶的父亲。

例题

思路

单点修改直接在线段树上 \(dfs\) 序的位置改。

查询像上面说的边跳别查就行。

代码

点击查看代码
const ll N=3e4+10,inf=1ll<<40;
ll n,q,w[N];
vector<ll>tu[N];
ll fa[N],dep[N],sz[N],son[N];//dfs1
ll cnt=0,top[N],dfn[N],rk[N];//dfs2
class SegmentTree{
public:
	class TREE{
	public:
		ll mx,va;
	}tr[N<<2];
	#define ls(p) (p<<1)
	#define rs(p) (p<<1|1)
	#define mx(p) tr[p].mx
	#define va(p) tr[p].va
	#define l_s(p) ls(p),l,mid
	#define r_s(p) rs(p),mid+1,r
	inline void Pushup(ll p){
		mx(p)=max(mx(ls(p)),mx(rs(p)));
		va(p)=va(ls(p))+va(rs(p));
	}
	void Build(ll p,ll l,ll r){
		if(l==r)mx(p)=va(p)=w[rk[l]];
		else{
			bdmd;
			Build(l_s(p));
			Build(r_s(p));
			Pushup(p);
		}
		return;
	}
	void Update(ll p,ll l,ll r,ll x,ll val){
		if(l>x||r<x)return;
		if(l==r)mx(p)=va(p)=val;
		else{
			bdmd;
			Update(l_s(p),x,val);
			Update(r_s(p),x,val);
			Pushup(p);
		}
		return;
	}
	ll QueryMax(ll p,ll l,ll r,ll le,ll ri){
		if(r<le||ri<l)return -inf;
		if(le<=l&&r<=ri)return mx(p);
		bdmd;
		return max(QueryMax(l_s(p),le,ri),QueryMax(r_s(p),le,ri));
	}
	ll QuerySum(ll p,ll l,ll r,ll le,ll ri){
		if(r<le||ri<l)return 0;
		if(le<=l&&r<=ri)return va(p);
		bdmd;
		return QuerySum(l_s(p),le,ri)+QuerySum(r_s(p),le,ri);
	}
}tr;
class KillTree{
public:
	void Dfs1(ll u){
		son[u]=-1,sz[u]=1;
		far(v,tu[u]){
			if(v==fa[u])continue;
			dep[v]=dep[u]+1,fa[v]=u;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[v]>sz[son[u]])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(v,tu[u])if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		return;
	}
	inline void Update(ll x,ll val){tr.Update(1,1,n,dfn[x],val);}
	inline ll QueryMax(ll x,ll y){
		ll num=-inf,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>=dep[ty])num=max(num,tr.QueryMax(1,1,n,dfn[tx],dfn[x])),x=fa[tx];
			else num=max(num,tr.QueryMax(1,1,n,dfn[ty],dfn[y])),y=fa[ty];
			tx=top[x],ty=top[y];
		}
		num=max(num,tr.QueryMax(1,1,n,min(dfn[x],dfn[y]),max(dfn[x],dfn[y])));
		return num;
	}
	inline ll QuerySum(ll x,ll y){
		ll num=0,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>=dep[ty])num+=tr.QuerySum(1,1,n,dfn[tx],dfn[x]),x=fa[tx];
			else num+=tr.QuerySum(1,1,n,dfn[ty],dfn[y]),y=fa[ty];
			tx=top[x],ty=top[y];
		}
		num+=tr.QuerySum(1,1,n,min(dfn[x],dfn[y]),max(dfn[x],dfn[y]));
		return num;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline void In(){
		n=rnt();
		_for(i,1,n-1){
			ll u=rnt(),v=rnt();
			tu[u].push_back(v);
			tu[v].push_back(u);
		}
		_for(i,1,n)w[i]=rnt();
		kt.Dfs1(1),kt.Dfs2(1,1),tr.Build(1,1,n);
		q=rnt();
		ll c=0;
		_for(i,1,q){
			char s[10];
			scanf("%s",s);
			ll x=rnt(),y=rnt();
			if(s[1]=='H')kt.Update(x,y);
			else if(s[1]=='M')printf("%lld\n",kt.QueryMax(x,y));
			else printf("%lld\n",kt.QuerySum(x,y));
		}
		return;
	}
}

练习题

染色

思路

教训:板子没写熟别着急做难题。

我们用线段树维护颜色段数量:如果线段树上一个节点两个儿子首尾颜色相同,将颜色段数量加起来后还要去掉 \(1\)

同一条链上颜色段数量的非常好维护,然后考虑如何合并不同链上的颜色段数量:

  1. 法一:记录上次跳完后的颜色,判断与现在的是否相等。

  2. 法二:当前节点跳到原链顶的父亲时,判断它与原链顶是否相等。

代码

点击查看代码
const ll N=2e5+10,inf=1ll<<40;
ll n,q,co[N],k[N];
vector<ll>tu[N];
ll fa[N],dep[N],sz[N],son[N];//dfs1
ll cnt=0,top[N],dfn[N],rk[N];//dfs2
class KillTree{
public:
	class SegmentTree{
	public:
		class TREE{public:ll hd,tl,cnt,tag;}tr[N<<2];
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define hd(p) tr[p].hd
		#define tl(p) tr[p].tl
		#define cn(p) tr[p].cnt
		#define ta(p) tr[p].tag
		#define l_s(p) ls(p),l,mid
		#define r_s(p) rs(p),mid+1,r
		inline void Pushup(ll p){
			hd(p)=hd(ls(p)),tl(p)=tl(rs(p));
			cn(p)=cn(ls(p))+cn(rs(p));
			if(tl(ls(p))==hd(rs(p)))--cn(p);
			return;
		}
		inline void Tag(ll p,ll color){
			cn(p)=1,ta(p)=color;
			hd(p)=color,tl(p)=color;
			return;
		}
		inline void Pushdown(ll p){
			if(ta(p)==-1)return;
			Tag(ls(p),ta(p));
			Tag(rs(p),ta(p));
			ta(p)=-1;
			return;
		}
		void Build(ll p,ll l,ll r){
			if(l==r)Tag(p,co[rk[l]]);
			else{
				bdmd;
				Build(l_s(p));
				Build(r_s(p));
				Pushup(p);
			}
			ta(p)=-1;
			return;
		}
		void Update(ll p,ll l,ll r,ll le,ll ri,ll color){
			if(ri<l||r<le)return;
			Pushdown(p);
			if(le<=l&&r<=ri)Tag(p,color);
			else{
				bdmd;
				Update(l_s(p),le,ri,color);
				Update(r_s(p),le,ri,color);
				Pushup(p);
			}
			return;
		}
		ll Query(ll p,ll l,ll r,ll le,ll ri){
			if(ri<l||r<le)return 0;
			Pushdown(p);
			if(le<=l&&r<=ri)return cn(p);
			else{
				bdmd;
				ll ans1=Query(l_s(p),le,ri);
				ll ans2=Query(r_s(p),le,ri);
				if(ans1>0&&ans2>0&&tl(ls(p))==hd(rs(p)))--ans1;
				return ans1+ans2;
			}
		}
		ll QueryP(ll p,ll l,ll r,ll x){
			if(x<l||r<x)return 0;
			Pushdown(p);
			if(l==r)return hd(p);
			else{
				bdmd;
				ll an=QueryP(l_s(p),x)+QueryP(r_s(p),x);
				return an;
			}
		}
	}tr;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(v,tu[u]){
			if(v==fa[u])continue;
			fa[v]=u,dep[v]=dep[u]+1;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[v]>sz[son[u]])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(v,tu[u])if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		return;
	}
	inline void Build(){fa[1]=1,Dfs1(1),Dfs2(1,1),tr.Build(1,1,n);}
	inline void Update(ll x,ll y,ll color){
		ll tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>=dep[ty])swap(x,y),swap(tx,ty);
			tr.Update(1,1,n,dfn[ty],dfn[y],color),y=fa[ty];
			tx=top[x],ty=top[y];
		}
		tr.Update(1,1,n,min(dfn[x],dfn[y]),max(dfn[x],dfn[y]),color);
	}
	inline ll Query(ll x,ll y){
		ll num=0,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>=dep[ty])swap(x,y),swap(tx,ty);
			num+=tr.Query(1,1,n,dfn[ty],dfn[y]),y=fa[ty];
			if(y&&tr.QueryP(1,1,n,dfn[ty])==tr.QueryP(1,1,n,dfn[y]))--num;
			tx=top[x],ty=top[y];
		}
		num+=tr.Query(1,1,n,min(dfn[x],dfn[y]),max(dfn[x],dfn[y]));
		return num;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void In(){
		n=rnt(),q=rnt();
		_for(i,1,n)co[i]=rnt();
		_for(i,1,n-1){
			ll u=rnt(),v=rnt();
			tu[u].push_back(v);
			tu[v].push_back(u);
		}
		kt.Build();
		_for(i,1,q){
			char op=rch();
			if(op=='C'){
				ll a=rnt(),b=rnt(),op=rnt();
				kt.Update(a,b,op);
			}
			else{
				ll a=rnt(),b=rnt();
				printf("%lld\n",kt.Query(a,b));
			}
		}
		return;
	}
}

QTREE

SPOJ 在机房上不去,垃圾洛谷 RemoteJudge 交不了 C++,只能用 Vjudge 凑合了。

思路

首先为了方便处理,我们化边权为点权,即把边权赋到深度较大的点上成为点权。

但是会有一个问题:查询到两点的 \(\text{LCA}\) 时,有可能多算一条边(即 \(\text{LCA}\) 与其父亲相连的那条边)。

那么我们暂时把它的点权设为 \(-\infty\),查询完再还原回来就可以了。

代码

点击查看代码
const ll N=2e4+10,inf=1ll<<40;
ll n,q,c[N],k[N];
class Edge{public:ll u,v,w;}e[N];
vector<pair<ll,ll> >tu[N];
ll fa[N],dep[N],sz[N],son[N];
ll cnt,top[N],dfn[N],rk[N];
class KillTree{
public:
	class SegmentTree{
	public:
		ll mx[N<<4];
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define l_s(p) ls(p),l,mid
		#define r_s(p) rs(p),mid+1,r
		void Build(ll p,ll l,ll r){
			if(l==r)mx[p]=c[rk[l]];
			else{
				bdmd;
				Build(l_s(p)),Build(r_s(p));
				mx[p]=max(mx[ls(p)],mx[rs(p)]);
			}
			return;
		}
		void Update(ll p,ll l,ll r,ll x,ll val){
			if(r<x||x<l)return;
			if(l==r)mx[p]=val;
			else{
				bdmd;
				Update(l_s(p),x,val),Update(r_s(p),x,val);
				mx[p]=max(mx[ls(p)],mx[rs(p)]);
			}
			return;
		}
		ll Query(ll p,ll l,ll r,ll le,ll ri){
			if(r<le||ri<l)return -inf;
			if(le<=l&&r<=ri)return mx[p];
			else{
				bdmd;
				return max(Query(l_s(p),le,ri),Query(r_s(p),le,ri));
			}
		}
	}tr;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(pr,tu[u]){
			ll v=pr.first;
			if(v==fa[u])continue;
			c[v]=pr.second;
			fa[v]=u,dep[v]=dep[u]+1;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(pr,tu[u]){
			ll v=pr.first;
			if(v!=fa[u]&&v!=son[u])
				Dfs2(v,v);
		}
		return;
	}
	inline void Build(){Dfs1(1),Dfs2(1,1),tr.Build(1,1,n);}
	inline void Update(ll x,ll y,ll z){
		if(fa[y]==x)swap(x,y);
		tr.Update(1,1,n,dfn[x],z);
		return;
	}
	inline ll GetLCA(ll x,ll y){
		ll tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>dep[ty])swap(x,y),swap(tx,ty);
			y=fa[ty],ty=top[y];
		}
		if(dep[x]>dep[y])swap(x,y);
		return x;
	}
	inline ll Query(ll x,ll y){
		ll lca=GetLCA(x,y),la=tr.Query(1,1,n,dfn[lca],dfn[lca]);
		tr.Update(1,1,n,dfn[lca],-inf);
		ll num=-inf,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]>dep[ty])swap(x,y),swap(tx,ty);
			num=max(num,tr.Query(1,1,n,dfn[ty],dfn[y])),y=fa[ty],ty=top[y];
		}
		num=max(num,tr.Query(1,1,n,min(dfn[x],dfn[y]),max(dfn[x],dfn[y])));
		tr.Update(1,1,n,dfn[lca],la);
		return num;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void Clear(){
		_for(i,1,n)tu[i].clear();
		memset(e,0,sizeof(e));
		memset(fa,0,sizeof(fa));
		memset(dep,0,sizeof(dep));
		memset(sz,0,sizeof(sz));
		memset(son,0,sizeof(son));
		memset(top,0,sizeof(top));
		memset(dfn,0,sizeof(dfn));
		memset(rk,0,sizeof(rk));
		memset(c,0,sizeof(c));
		memset(k,0,sizeof(k));
		memset(kt.tr.mx,0,sizeof(kt.tr.mx));
		cnt=0;
	}
	inline void In(){
		Clear();
		n=rnt();
		_for(i,1,n-1){
			ll u=rnt(),v=rnt(),w=rnt();
			e[i]=(Edge){u,v,w};
			tu[u].push_back(make_pair(v,w));
			tu[v].push_back(make_pair(u,w));
		}
		kt.Build();
		while(1){
			char op=rch();
			if(op=='C'){
				ll a=rnt(),b=rnt();
				kt.Update(e[a].u,e[a].v,b);
			}
			else if(op=='Q'){
				ll a=rnt(),b=rnt();
				printf("%lld\n",kt.Query(a,b));
			}
			else return;
		}
		return;
	}
}

[HAOI2015]树上操作

思路

操作 \(1,3\) 都是板子,很好求。考虑如何求操作 \(2\)

可以发现,一棵子树内的 \(\text{DFS}\) 序相同,而我们在求重儿子时恰好也求了子树大小。

那么我们更新一个节点 \(u\) 的子树时,直接在线段树上更改区间 \([dfn_u,dfn_u+size_u-1]\) 即可。

代码

点击查看代码
using namespace std;
const ll N=2e5+10,inf=1ll<<40;
ll n,q,c[N];
vector<ll>tu[N];
ll fa[N],dep[N],sz[N],son[N];
ll cnt,top[N],dfn[N],rk[N];
class KillTree{
public:
	class SegmentTree{
	public:
		class TREE{public:ll val,sz,tag;}tr[N<<2];
		#define va(p) tr[p].val
		#define sz(p) tr[p].sz
		#define ta(p) tr[p].tag
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define l_s(p) ls(p),l,mid
		#define r_s(p) rs(p),mid+1,r
		inline void Tag(ll p,ll tg){va(p)+=tg*sz(p),ta(p)+=tg;}
		inline void PushUp(ll p){va(p)=va(ls(p))+va(rs(p));}
		inline void PushDown(ll p){
			if(!ta(p))return;
			Tag(ls(p),ta(p));
			Tag(rs(p),ta(p));
			ta(p)=0;
			return;
		}
		void Build(ll p,ll l,ll r){
			sz(p)=r-l+1,ta(p)=0;
			if(l==r)va(p)=c[rk[l]];
			else{
				bdmd;
				Build(l_s(p));
				Build(r_s(p));
				PushUp(p);
			}
			return;
		}
		void Update(ll p,ll l,ll r,ll le,ll ri,ll val){
			if(ri<l||r<le)return;
			PushDown(p);
			if(le<=l&&r<=ri)Tag(p,val);
			else{
				bdmd;
				Update(l_s(p),le,ri,val);
				Update(r_s(p),le,ri,val);
				PushUp(p);
			}
			return;
		}
		ll Query(ll p,ll l,ll r,ll le,ll ri){
			if(ri<l||r<le)return 0;
			PushDown(p);
			if(le<=l&&r<=ri)return va(p);
			else{
				bdmd;
				ll ans1=Query(l_s(p),le,ri);
				ll ans2=Query(r_s(p),le,ri);
				return ans1+ans2;
			}
		}
	}tr;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(v,tu[u]){
			if(v==fa[u])continue;
			fa[v]=u,dep[v]=dep[u]+1;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(v,tu[u])if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		return; 
	}
	inline void Build(){Dfs1(1),Dfs2(1,1),tr.Build(1,1,n);}
	inline void UpdateP(ll x,ll val){tr.Update(1,1,n,dfn[x],dfn[x],val);}
	inline void UpdateR(ll x,ll val){tr.Update(1,1,n,dfn[x],dfn[x]+sz[x]-1,val);}
	inline ll Query(ll x){
		ll num=0,tx=top[x];
		while(tx>=1){
			num+=tr.Query(1,1,n,dfn[tx],dfn[x]);
			x=fa[tx],tx=top[x];
		}
		return num;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void In(){
		n=rnt(),q=rnt();
		_for(i,1,n)c[i]=rnt();
		_for(i,1,n-1){
			ll u=rnt(),v=rnt();
			tu[u].push_back(v);
			tu[v].push_back(u);
		}
		kt.Build();
		_for(i,1,q){
			ll op=rnt(),a=rnt(),b=0;
			if(op==1)b=rnt(),kt.UpdateP(a,b);
			else if(op==2)b=rnt(),kt.UpdateR(a,b);
			else printf("%lld\n",kt.Query(a));
		}
		return;
	}
}

[NOIP2013 提高组] 货车运输

思路

给出的不是一棵树,但是我们可以发现查询只与路径上最小值相关,而不管距离,所以我们只留下一些有用的边即可,即求出一棵最小生成树。

剩下的就是板子了。

代码

点击查看代码
const ll N=1e5+10,inf=1ll<<40;
ll n,m,q,c[N],k[N];
vector<pair<ll,ll> >tu[N];
ll fa[N],dep[N],sz[N],son[N];
ll cnt,top[N],dfn[N],rk[N];
class BCJ{
public:
	ll f[N];
	inline void Pre(){_for(i,1,n)f[i]=i;}
	ll Find(ll x){return (f[x]==x)?x:(f[x]=Find(f[x]));}
	inline void Merge(ll x,ll y){f[y]=x;}
}bcj;
namespace Kru{
	class Edge{
	public:
		ll u,v,w;
		inline bool operator<(const Edge &d)const{return w>d.w;}
	}e[N];
	inline void Add(ll i,ll u,ll v,ll w){e[i].u=u,e[i].v=v,e[i].w=w;}
	inline void Solve(){
		sort(e+1,e+m+1);
		ll k=0;
		bcj.Pre();
		_for(i,1,m){
			ll fu=bcj.Find(e[i].u),fv=bcj.Find(e[i].v);
			if(fu!=fv){
				tu[e[i].u].push_back(make_pair(e[i].v,e[i].w));
				tu[e[i].v].push_back(make_pair(e[i].u,e[i].w));
				bcj.Merge(fu,fv);
				++k;
			}
			if(k==n-1)break;
		}
		return;
	}
}
class KillTree{
public:
	class SegmentTree{
	public:
		class TREE{public:ll mn;}tr[N<<2];
		#define mn(p) tr[p].mn
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define l_s(p) (p<<1),l,mid
		#define r_s(p) (p<<1|1),mid+1,r
		void Build(ll p,ll l,ll r){
			if(l==r)mn(p)=c[rk[l]]?c[rk[l]]:inf;
			else{
				bdmd;
				Build(l_s(p)),Build(r_s(p));
				mn(p)=min(mn(ls(p)),mn(rs(p)));
			}
			return;
		}
		ll Query(ll p,ll l,ll r,ll le,ll ri){
			if(ri<l||r<le)return inf;
			if(le<=l&&r<=ri)return mn(p);
			else{
				bdmd;
				return min(Query(l_s(p),le,ri),Query(r_s(p),le,ri));
			}
		}
	}tr;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(pr,tu[u]){
			ll v=pr.first,w=pr.second;
			if(v==fa[u])continue;
			c[v]=w,fa[v]=u,dep[v]=dep[u]+1;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(pr,tu[u]){
			ll v=pr.first;
			if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		}
		return;
	}
	inline void Build(){Dfs1(1),Dfs2(1,1),tr.Build(1,1,n);}
	inline ll Query(ll x,ll y){
		if(bcj.Find(x)!=bcj.Find(y))return -1;
		ll num=inf,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
			num=min(num,tr.Query(1,1,n,dfn[tx],dfn[x]));
			x=fa[tx],tx=top[x];
		}
		if(x!=y)num=min(num,tr.Query(1,1,n,min(dfn[x],dfn[y])+1,max(dfn[x],dfn[y])));
		return num;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void In(){
		n=rnt(),m=rnt();
		_for(i,1,m){
			ll u=rnt(),v=rnt(),w=rnt();
			Kru::Add(i,u,v,w);
		}
		Kru::Solve();
		kt.Build();
		q=rnt();
		_for(i,1,q){
			ll x=rnt(),y=rnt();
			printf("%lld\n",kt.Query(x,y));
		}
		return;
	}
}

[NOIP2015 提高组] 运输计划

思路

毒瘤好题。

首先老套路化边权为点权。

考虑去掉哪一条边才会产生贡献:显然是耗时最久的飞船经过的某条边。

那么我们维护一个数组 \(q\)\(q_i\) 表示不经过节点 \(i\) 的飞船中的最长时间,这样只要枚举耗时最久的飞船经过的所有边就可以了。

那么如何维护一个数组 \(q\) 呢?我们可以发现两点之间的路径会被树链截成若干个不相交的 \(\text{dfs}\) 序区间,那么我们对这些区间排个序,将相邻区间之间形成的区间打上当前答案的标记即可。

然后说一下我踩过的坑:

  • 读错题,飞船应是同时出发的。(\(0\text{pts}\longrightarrow15\text{pts}\)

  • #define ls(p) (p<<2)\(15\text{pts}\longrightarrow55\text{pts}\)

  • 新来的标记把原标记覆盖了,但可能原标记才是有用的。(\(55\text{pts}\longrightarrow95\text{pts}\)

  • 答案可能为 \(0\),但由于 \(x=y\)\(\text{ans}\) 压根就没更新,还是 \(\infty\),可以通过 \(\bmod{\infty}\) 解决。(\(95\text{pts}\longrightarrow100\text{pts}\)

代码

点击查看代码
const ll N=3e6+10,inf=1ll<<40;
ll n,m,q,c[N],ans=inf,lgst,lx,ly;
vector<pair<ll,ll> >tu[N];
ll fa[N],dep[N],sz[N],son[N];
ll cnt,top[N],dfn[N],rk[N];
class KillTree{
public:
	class TreeArray{
	public:
		ll b[N];
		inline void Update(ll x,ll y){while(x&&x<=n)b[x]+=y,x+=(x&-x);}
		inline ll Query(ll x){ll sum=0;while(x)sum+=b[x],x-=(x&-x);return sum;}
		inline void Build(){_for(i,1,n)Update(i,c[rk[i]]);}
	}tra;
	class SegmentTree{
	public:
		class Tree{public:ll mx,tag,sz;}tr[N<<2];
		#define sz(p) tr[p].sz
		#define mx(p) tr[p].mx
		#define ta(p) tr[p].tag
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define l_s(p) (p<<1),l,mid
		#define r_s(p) (p<<1|1),mid+1,r
		inline void Tag(ll p,ll tg){if(tg>ta(p))ta(p)=tg,mx(p)=max(mx(p),tg);}
		inline void PushUp(ll p){mx(p)=max(mx(ls(p)),mx(rs(p)));}
		inline void PushDown(ll p){if(ta(p))Tag(ls(p),ta(p)),Tag(rs(p),ta(p)),ta(p)=0;}
		void Build(ll p,ll l,ll r){
			ta(p)=0,sz(p)=r-l+1;
			if(l==r)mx(p)=0;
			else{
				bdmd;
				Build(l_s(p)),Build(r_s(p));
				PushUp(p);
			}
			return;
		}
		void Update(ll p,ll l,ll r,ll le,ll ri,ll val){
			if(ri<l||r<le)return;
			PushDown(p);
			if(le<=l&&r<=ri)Tag(p,val);
			else{
				bdmd;
				Update(l_s(p),le,ri,val);
				Update(r_s(p),le,ri,val);
				PushUp(p);
			}
		}
		ll Query(ll p,ll l,ll r,ll x){
			if(x<l||r<x)return 0;
			PushDown(p);
			if(l==r)return mx(p);
			else{
				bdmd;
				return max(Query(l_s(p),x),Query(r_s(p),x));
			}
		}
	}set;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(pr,tu[u]){
			ll v=pr.first,w=pr.second;
			if(v==fa[u])continue;
			fa[v]=u,dep[v]=dep[u]+1,c[v]=w;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(pr,tu[u]){
			ll v=pr.first;
			if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		}
		return;
	}
	inline void Build(){Dfs1(1),Dfs2(1,1),tra.Build(),set.Build(1,1,n);}
	inline ll Query(ll x,ll y){
		ll num=0,tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
			num+=tra.Query(dfn[x])-tra.Query(dfn[tx]-1);
			x=fa[tx],tx=top[x];
		}
		num+=tra.Query(max(dfn[x],dfn[y]))-tra.Query(min(dfn[x],dfn[y]));
		return num;
	}
	inline void Add(ll x,ll y){
		ll tx=top[x],ty=top[y],num=Query(x,y);
		if(num>lgst)lgst=num,lx=x,ly=y;
		vector<pair<ll,ll> >e;
		while(tx!=ty){
			if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
			e.push_back(make_pair(dfn[tx],dfn[x]));
			x=fa[tx],tx=top[x];
		}
		if(dep[x]<dep[y])swap(x,y);
		e.push_back(make_pair(dfn[y]+1,dfn[x]));
		sort(e.begin(),e.end());
		if(!e.empty()){
			ll sz=e.size()-1;
			if(e[0].first!=1)set.Update(1,1,n,1,e[0].first-1,num);
			if(e[sz].second!=n)set.Update(1,1,n,e[sz].second+1,n,num);
			_for(i,1,sz){
				if(e[i-1].second+1==e[i].first)continue;
				set.Update(1,1,n,e[i-1].second+1,e[i].first-1,num);
			}
		}
		return;
	}
	inline ll Solve(){
		while(lx!=ly){
			if(dep[lx]<dep[ly])swap(lx,ly);
			ans=min(ans,max(lgst-c[lx],set.Query(1,1,n,dfn[lx])));
			lx=fa[lx];
		}
		return ans;
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void In(){
		n=rnt(),m=rnt();
		_for(i,1,n-1){
			ll u=rnt(),v=rnt(),w=rnt();
			tu[u].push_back(make_pair(v,w));
			tu[v].push_back(make_pair(u,w));
		}
		kt.Build();
		_for(i,1,m){
			ll x=rnt(),y=rnt();
			if(x!=y)kt.Add(x,y);
		}
		printf("%lld\n",kt.Solve()%inf);
		return;
	}
}

遥远的国度

思路

只要想清楚了就很简单。

开个变量 \(\text{root}\) 记录当前根,修改是常规树剖。

然后考虑如何查询,我们设当前查询节点 \(u\),然后分情况讨论:

  1. \(u\) 就是 \(\text{root}\):直接查询整棵树。

  1. \(\text{root}\)\(u\) 祖先:直接查询以 \(u\) 为根的子树。

  1. \(u\)\(\text{root}\) 祖先:直接把以 \(u\) 为根的子树刨去。即设以 \(u\) 为根的子树对应的区间为 \([l,r]\),则查询区间 \([1,l-1]\)\([r+1,n]\) 的最小值。

代码

点击查看代码
const ll N=2e5+10,inf=1ll<<40;
ll n,m,root,val[N],ans;
vector<ll>tu[N];
ll fa[N],dep[N],sz[N],son[N];
ll cnt,top[N],dfn[N],rk[N];
class KillTree{
public:
	class SegmentTree{
	public:
		class{public:ll va,ta;}tr[N<<2];
		#define va(p) tr[p].va
		#define ta(p) tr[p].ta
		#define ls(p) (p<<1)
		#define rs(p) (p<<1|1)
		#define l_s(p) (p<<1),l,mid
		#define r_s(p) (p<<1|1),mid+1,r
		inline void Tag(ll p,ll tg){va(p)=ta(p)=tg;return;}
		inline void PushUp(ll p){va(p)=min(va(ls(p)),va(rs(p)));}
		inline void PushDown(ll p){
			if(ta(p)==-1)return;
			Tag(ls(p),ta(p));
			Tag(rs(p),ta(p));
			ta(p)=-1;
			return;
		}
		void Build(ll p,ll l,ll r){
			ta(p)=-1;
			if(l==r)va(p)=val[rk[l]];
			else{
				bdmd;
				Build(l_s(p)),Build(r_s(p));
				PushUp(p);
			}
			return;
		}
		void Update(ll p,ll l,ll r,ll le,ll ri,ll val){
			if(ri<l||r<le)return;
			PushDown(p);
			if(le<=l&&r<=ri)Tag(p,val);
			else{
				bdmd;
				Update(l_s(p),le,ri,val);
				Update(r_s(p),le,ri,val);
				PushUp(p);
			}
			return;
		}
		ll Query(ll p,ll l,ll r,ll le,ll ri){
			if(ri<l||r<le)return inf;
			PushDown(p);
			if(le<=l&&r<=ri)return va(p);
			else{
				bdmd;
				return min(Query(l_s(p),le,ri),Query(r_s(p),le,ri));
			}
		}
	}tr;
	void Dfs1(ll u){
		sz[u]=1,son[u]=-1;
		far(v,tu[u]){
			if(v==fa[u])continue;
			fa[v]=u,dep[v]=dep[u]+1;
			Dfs1(v);
			sz[u]+=sz[v];
			if(son[u]==-1||sz[son[u]]<sz[v])son[u]=v;
		}
		return;
	}
	void Dfs2(ll u,ll t){
		top[u]=t;
		dfn[u]=++cnt;
		rk[cnt]=u;
		if(son[u]==-1)return;
		Dfs2(son[u],t);
		far(v,tu[u])if(v!=fa[u]&&v!=son[u])Dfs2(v,v);
		return;
	}
	inline void Build(){Dfs1(1),Dfs2(1,1),tr.Build(1,1,n);}
	inline void Update(ll x,ll y,ll z){
		ll tx=top[x],ty=top[y];
		while(tx!=ty){
			if(dep[tx]<dep[ty])swap(x,y),swap(tx,ty);
			tr.Update(1,1,n,dfn[tx],dfn[x],z);
			x=fa[tx],tx=top[x];
		}
		if(dep[x]<dep[y])swap(x,y);
		tr.Update(1,1,n,dfn[y],dfn[x],z);
	}
	inline ll Query(ll x,ll y){
		ll q=x,tx=top[x],ty=top[y],s=x;
		while(tx!=ty){
			if(dep[tx]<dep[ty])s=y,y=fa[ty],ty=top[y];
			else x=fa[tx],tx=top[x];
		}
		if(x!=y)s=son[x];
		if(dep[x]>dep[y])swap(x,y);
		if(q==root)return tr.Query(1,1,n,1,n);
		else if(x!=q)return tr.Query(1,1,n,dfn[q],dfn[q]+sz[q]-1);
		else return min(tr.Query(1,1,n,1,dfn[s]-1),tr.Query(1,1,n,dfn[s]+sz[s],n));
	}
}kt;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline void In(){
		n=rnt(),m=rnt(),root=1;
		_for(i,1,n-1){
			ll u=rnt(),v=rnt();
			tu[u].push_back(v);
			tu[v].push_back(u);
		}
		_for(i,1,n)val[i]=rnt();
		root=rnt();
		kt.Build();
		_for(i,1,m){
			ll op=rnt();
			if(op==1)root=rnt();
			else if(op==2){
				ll x=rnt(),y=rnt(),z=rnt();
				kt.Update(x,y,z);
			}
			else{
				ll x=rnt();
				printf("%lld\n",kt.Query(x,root));
			}
		}
		return;
	}
}

\[\Huge{\mathfrak{The\ End}} \]

有关「学习笔记」树链剖分的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  3. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  6. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  7. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  8. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  9. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  10. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

随机推荐