「学习笔记」树链剖分

「学习笔记」树链剖分

点击查看目录

树链剖分

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

算法

By OI-Wiki:

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

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

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

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

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

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

如图:
image

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

实现

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

  • 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\)

一棵子树内的 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\) 为根的子树。

image

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

image

代码

点击查看代码
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}} \]

posted @ 2022-08-01 15:37  K8He  阅读(97)  评论(1编辑  收藏  举报