树链剖分(一)-重链剖分:模板&例题

本文选取的例题如下:

T1:洛谷P2590 [ZJOI2008]树的统计 & YBTOJ-A. 【例题1】树的统计

T2:洛谷P2146 [NOI2015] 软件包管理器 & YBTOJ-B. 软件管理

T3:洛谷P2486 [SDOI2011]染色 & YBTOJ-C. 树上染色

T4:洛谷P3313 [SDOI2014]旅行 & YBTOJ-D. 旅行留宿

一、树链剖分基础:

(一)树链剖分基本思想:

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。

具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。

树链剖分(树剖/链剖)有多种形式,如 重链剖分,长链剖分 和用于 Link/cut Tree 的剖分(有时被称作“实链剖分”),大多数情况下(没有特别说明时),“树链剖分”都指“重链剖分”。

本文所探讨的就是重链剖分。

(二)树链剖分能够解决的问题:

<1>修改 树上两点之间的路径上 所有点的值。
<2>查询 树上两点之间的路径上 节点权值的 和/极值/其它(在序列上可以用数据结构维护,便于合并的信息。
<3>修改一个节点的子树中所有节点的值,或者求一个节点的子树中所有节点的值之和。
<4>求LCA
具体的实现就是将树拆分成若干条链,通过一定的编号方式使得每一条重链上的点的编号是连续的。
这一条重链就能对应区间 [ 1 , n ] [1,n] [1,n]上的一段连续区间,因此可以直接用 线段树树状数组(?)平衡树可持久化线段树(主席树)动态开点线段树 等数据结构维护区间信息。

二、树链剖分模板:

(一)基础定义&需要维护的参量

重儿子:节点 i i i的重儿子,即 i i i的所有子节点中子树最大的点
重链:若干个节点的重儿子连成的一条链,落单的节点也被认为是一条重链(该重链只有一个点)
top[i]:节点 i i i所在的重链的顶部,即该重链上深度最小的点

一般来说维护的信息有如下几个
int ecnt=-1,head[maxn]int w[maxn],wt[maxn]; //w表示树上节点的权值,wt表示该节点对应的区间上的点的值
int sum[maxn<<2],add[maxn<<2];//用于线段树的维护
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 
//son[i]是节点i的重儿子
//id[i]是节点i在区间上对应的点的编号
//fa[i]是节点i的父节点
//dep[i]是节点i的深度
//siz[i]是节点i的子树的大小(包括节点i)
//top[i]是节点i所在的重链的顶端

(二)树链剖分的具体实现:

树链剖分的过程分为两部分: dfs1 和 dfs2
DFS1
从根节点开始(若未给出根节点,以1作为根节点即可),遍历整棵树。
求出以下每个节点的以下信息:

f a [ i ] , d e p [ i ] , s o n [ i ] , s i z [ i ] fa[i],dep[i],son[i],siz[i] fa[i],dep[i],son[i],siz[i]

inline void dfs1(int x,int f,int deep)
{
	dep[x]=deep; 
	fa[x]=f; 
	siz[x]=1; 
	int maxson=-1; 
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==f)continue; 
		dfs1(v,x,deep+1); 
		siz[x]+=siz[v];
		if(siz[v]>maxson)son[x]=v,maxson=siz[v];
	}
}

DFS2
依旧是从根节点开始,遍历整颗树,这次的目的是求出每一条重链以及各个节点在区间 [ 1 , n ] [1,n] [1,n]上的对应编号。
为了是每一条重链上的节点连续,需要优先遍历每一个节点的重儿子。

inline void dfs2(int x,int topf)
{
	id[x]=++cnt;
	wt[cnt]=w[x]; 
	top[x]=topf; 
	if(!son[x]) return; 
	dfs2(son[x],topf); 
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x]||v==son[x])continue;
		dfs2(v,v); 
	}
}

(三):完整模板&模板题

题目在这里:P3384 【模板】轻重链剖分/树链剖分

一共有四个操作:
1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。

2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。

3 x z,表示将以 x 为根节点的子树内所有节点值都加上 zz。

4 x 表示求以 x 为根节点的子树内所有节点值之和

操作3、4:
很简单.
因为一个节点 i i i的子树内的所有点的在区间上的对应编号范围是 [ i d [ i ] , i d [ i ] + s i z [ i ] − 1 ] [id[i],id[i]+siz[i]-1] [id[i],id[i]+siz[i]1],
直接做区间修改和区间求和即可。

操作1、2
稍微复杂。
运用了树链剖分求LCA的思想。
联想一下倍增求LCA的过程,就能很容易地拓展过来。
当两个点并未位于同一条重链上时,让深度较大的点跳到它所在的重链的顶端,不断重复该过程,
直到两个点位于同一条重链上为止。
易得 每一条重链上不会出现两个点深度相同,则此时必有深度较小的点为深度较大的点的祖先节点。
可求出LCA即为深度较小的点。

至于如何进行操作1、2,
只需在进行该过程时不断对跳过的重链进行区间求和和区间修改即可。

inline void update(int k,int l,int r,int x,int y,int z)
{
    if(x<=l&&r<=y)
	{
		add[k]+=z;
		sum[k]=(sum[k]+z*(r-l+1)%mod)%mod;
		return;
	}
	if(add[k]) pushdown(k,l,r);
	if(x<=mid) update(lson,l,mid,x,y,z);
	if(y>mid) update(rson,mid+1,r,x,y,z);
	sum[k]=(sum[k<<1]+sum[k<<1|1])%mod;
}

inline int qRange(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{ 
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ans=(ans+query(1,1,n,id[x],id[y]))%mod;
	return ans;
}

最后是完整的模板

#include
using namespace std;

#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)

const int maxn=2e5+100;
int n,m,r,mod;
int ecnt=-1,head[maxn],w[maxn],wt[maxn]; 
int sum[maxn<<2],add[maxn<<2];
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn]; 

struct mint
{
	int nxt,v;	
}e[maxn];

inline void addline(int u,int v)
{
    e[++ecnt].nxt=head[u];
    e[ecnt].v=v;
    head[u]=ecnt;
}
 
inline void pushdown(int k,int l,int r)
{
	add[lson]+=add[k];
	add[rson]+=add[k];
    sum[lson]=(sum[lson]+add[k]*(mid-l+1)%mod)%mod;
    sum[rson]=(sum[rson]+add[k]*(r-mid)%mod)%mod;
    add[k]=0;
}

inline void build(int k,int l,int r)
{
    if(l==r)
	{
        sum[k]=wt[l]%mod;
        return;
    }
    build(lson,l,mid);
    build(rson,mid+1,r);
    sum[k]=(sum[k<<1]+sum[k<<1|1])%mod;
}

inline int query(int k,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return sum[k];
	if(add[k])pushdown(k,l,r);
	int res=0;
	if(x<=mid) res=(res+query(lson,l,mid,x,y))%mod;
	if(y>mid) res=(res+query(rson,mid+1,r,x,y))%mod;
	return res;
}

inline void update(int k,int l,int r,int x,int y,int z)
{
    if(x<=l&&r<=y)
	{
		add[k]+=z;
		sum[k]=(sum[k]+z*(r-l+1)%mod)%mod;
		return;
	}
	if(add[k]) pushdown(k,l,r);
	if(x<=mid) update(lson,l,mid,x,y,z);
	if(y>mid) update(rson,mid+1,r,x,y,z);
	sum[k]=(sum[k<<1]+sum[k<<1|1])%mod;
}

inline int qRange(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{ 
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	ans=(ans+query(1,1,n,id[x],id[y]))%mod;
	return ans;
}

inline void updRange(int x,int y,int k)
{
	k%=mod;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,1,n,id[x],id[y],k);
}

inline int qSon(int x)
{
	return query(1,1,n,id[x],id[x]+siz[x]-1); 
}

inline void updSon(int x,int k)
{
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

inline void dfs1(int x,int f,int deep)
{
	dep[x]=deep; 
	fa[x]=f; 
	siz[x]=1; 
	int maxson=-1; 
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==f)continue; 
		dfs1(v,x,deep+1); 
		siz[x]+=siz[v];
		if(siz[v]>maxson)son[x]=v,maxson=siz[v];
	}
}

inline void dfs2(int x,int topf)
{
	id[x]=++cnt;
	wt[cnt]=w[x]; 
	top[x]=topf; 
	if(!son[x]) return; 
	dfs2(son[x],topf); 
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x]||v==son[x])continue;
		dfs2(v,v); 
	}
}

int main()
{
	memset(head,-1,sizeof(head)); 
	scanf("%d%d%d%d",&n,&m,&r,&mod); 
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<=n-1;i++)
	{
        int u,v;
        scanf("%d%d",&u,&v);
        addline(u,v);
		addline(v,u);
    }
    dfs1(r,0,1);
    dfs2(r,r);
    build(1,1,n);
	for(int i=1;i<=m;++i)
	{
        int k,x,y,z;
        scanf("%d",&k);
        if(k==1)
		{
            scanf("%d%d%d",&x,&y,&z);
            updRange(x,y,z);
            continue;
        }
        if(k==2)
		{
            scanf("%d%d",&x,&y);
            printf("%d\n",qRange(x,y));
        	continue;
		}
        if(k==3)
		{
            scanf("%d%d",&x,&y);
            updSon(x,y);
        	continue;
		}
        if(k==4)
		{
            scanf("%d",&x); 
            printf("%d\n",qSon(x));
    		continue;
		}
    }
}

三、例题:

T1:洛谷P2590 [ZJOI2008]树的统计 & YBTOJ-A. 【例题1】树的统计

重链剖分模板+线段树维护单点覆盖(修改)、区间求和、区间极值。

#include
using namespace std;
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
const int maxn=3e5+50,INF=1e9+7;
int n,m,r;
int ecnt=-1,head[maxn],w[maxn],wt[maxn];
int sum[maxn<<2],Max[maxn<<2];
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn];
struct mint
{
	int nxt,v;	
}e[maxn<<1];
inline void addline(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].v=v;
	head[u]=ecnt;	
}
inline void dfs1(int x,int f,int deeep)
{
	dep[x]=deeep;
	fa[x]=f;
	siz[x]=1;
	int maxson=-1;
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==f) continue;
		dfs1(v,x,deeep+1);
		siz[x]+=siz[v];
		if(siz[v]>maxson) son[x]=v,maxson=siz[v];
	}
}
inline void dfs2(int x,int topf)
{
	id[x]=++cnt;
	wt[cnt]=w[x];
	top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x] || v==son[x]) continue;
		dfs2(v,v);
	}
}

void pushup(int k,int l,int r)
{
	sum[k]=sum[lson]+sum[rson];
	Max[k]=max(Max[lson],Max[rson]);
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		sum[k]=Max[k]=wt[l];
		return;
	}
	build(lson,l,mid);
	build(rson,mid+1,r);
	pushup(k,l,r);
}

void change(int k,int l,int r,int x,int t)
{
	if(l==r)
	{
		sum[k]=t;
		Max[k]=t;
		return;
	}
	if(x<=mid) change(lson,l,mid,x,t);
	else change(rson,mid+1,r,x,t);
	pushup(k,l,r); 
}

int querysum(int k,int l,int r,int x,int y)
{
	if(x<=l && r<=y) return sum[k];
	int res=0;
	if(x<=mid) res+=querysum(lson,l,mid,x,y);
	if(y>mid) res+=querysum(rson,mid+1,r,x,y);
	return res; 
}

int querymax(int k,int l,int r,int x,int y)
{
	if(x<=l && r<=y) return Max[k];
	int res=-INF;
	if(x<=mid) res=max(res,querymax(lson,l,mid,x,y));
	if(y>mid) res=max(res,querymax(rson,mid+1,r,x,y));
	return res;	
}

int qmax(int x,int y)
{
	int res=-INF;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=max(res,querymax(1,1,n,id[top[x]],id[x]));
		x=fa[top[x]];	
	}
	if(dep[x]>dep[y]) swap(x,y);
	return res=max(res,querymax(1,1,n,id[x],id[y]));
}	

int qsum(int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res+=querysum(1,1,n,id[top[x]],id[x]);
		x=fa[top[x]];	
	}
	if(dep[x]>dep[y]) swap(x,y);
	return res+=querysum(1,1,n,id[x],id[y]);
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	fill(Max+1,Max+(n<<1)+1,-INF);
	for(int i=1;i<=n-1;++i)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		addline(x,y);
		addline(y,x);	
	}
	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
	scanf("%d",&m);
	r=1;
	dfs1(r,0,1);
	dfs2(r,r);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		int u,v;
		char s[10];
		cin>>s;
		scanf("%d%d",&u,&v);
		if(s[1]=='H')
		{
			change(1,1,n,id[u],v);	
		}
		if(s[1]=='M') printf("%d\n",qmax(u,v));
		if(s[1]=='S') printf("%d\n",qsum(u,v));	
	}	
	return 0;
}

T2:洛谷P2146 [NOI2015] 软件包管理器 & YBTOJ-B. 软件管理

解释一下题意。
删除一个软件就是统计其子树中已安装程序的个数并将它的子树清空。
安装一个软件就是统计该节点的根节点的路径上有多少已安装的程序,并将该路径上未安装的程序全部安装。

考虑节点权值的意义,1表示该程序已被安装,0表示该程序未被安装。
则删除一个软件就是统计其子树的权值和,并将其子树代表的区间中的所有点的权值修改成0.
安装一个软件就是统计该节点到根节点的路径上的权值和,并将该路径上所有点的权值修改成1.

线段树维护区间覆盖和区间求和即可。

注意一个细节:
区间覆盖就是把区间修改中的“+=”操作全部改成“=”即可。这一点对于add和sum都是成立的。

Code

//删除一个节点->查询其子树大小
//安装一个节点[a]->dep[a]-最近已安装点数
#include
using namespace std;
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
const int maxn=1e5+50;
int dep[maxn],fa[maxn],siz[maxn],son[maxn],top[maxn];
int id[maxn],cnt;
int n,m,r=0;
int head[maxn],ecnt=-1;
int tag[maxn<<2],sum[maxn<<2];
struct mint
{
	int nxt,v;	
}e[maxn<<1];
inline void addline(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].v=v;
	head[u]=ecnt;	
}

void dfs1(int x,int f,int deeep)
{
	siz[x]=1;
	fa[x]=f;
	dep[x]=deeep;
	int maxson=-1;
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		dfs1(v,x,deeep+1);
		siz[x]+=siz[v];
		if(siz[v]>maxson) son[x]=v,maxson=siz[v];	
	}
}	

void dfs2(int x,int topf)
{
	id[x]=++cnt;
	top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x] || v==son[x]) continue;
		dfs2(v,v);
	}
}

void pushup(int k)
{
	sum[k]=sum[lson]+sum[rson];	
}

void pushdown(int k,int l,int r)
{
	tag[lson]=tag[rson]=tag[k];
	sum[lson]=tag[k]*(mid-l+1);
	sum[rson]=tag[k]*(r-mid);
	tag[k]=-1; 
}

void change(int k,int l,int r,int x,int y,int t)
{
	if(x<=l && r<=y)
	{
		sum[k]=t*(r-l+1);
		tag[k]=t;
		return;
	}
	if(x<=mid) change(lson,l,mid,x,y,t);
	if(y>mid) change(rson,mid+1,r,x,y,t);
	pushup(k);
}	

int querysum(int k,int l,int r,int x,int y)
{
	if(x<=l && r<=y) return sum[k];
	int res=0;
	if(tag[k]!=-1) pushdown(k,l,r);
	if(x<=mid) res+=querysum(lson,l,mid,x,y);
	if(y>mid) res+=querysum(rson,mid+1,r,x,y);
	return res;	
}

int querypath(int x)
{
	int res=0;
	while(top[x]!=1)
	{
		res+=querysum(1,1,n,id[top[x]],id[x]);
		change(1,1,n,id[top[x]],id[x],1);
		x=fa[top[x]];
	}
	res+=querysum(1,1,n,1,id[x]);
	change(1,1,n,1,id[x],1);
	return res;
}

int main()
{
	memset(tag,-1,sizeof(tag));
	memset(head,-1,sizeof(head));
	scanf("%d",&n);
	for(int i=2;i<=n;++i)
	{
		int x;
		scanf("%d",&x);
		++x;
		addline(x,i);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	scanf("%d",&m);
	for(int i=1;i<=m;++i)
	{
		char s[20];
		cin>>s;
		int w;
		scanf("%d",&w);
		++w;
		if(s[0]=='i')
		{
			printf("%d\n",dep[w]-querypath(w)); 
		}
		else
		{
			printf("%d\n",querysum(1,1,n,id[w],id[w]+siz[w]-1));
			change(1,1,n,id[w],id[w]+siz[w]-1,0);
		}
	}
	return 0;
}

T3:洛谷P2486 [SDOI2011]染色 & YBTOJ-C. 树上染色

这道题要求完成区间覆盖和区间求和。
注意类似“求颜色段数”的问题,大多数都采取从小到大合并。
每次合并两个区间时,
若边界上的两点颜色相同,则合并出的大区间颜色段数就是两个区间中的颜色段数之和-1。
若不同,则为两个小区间中的颜色段数之和。

虽说题目描述是修改颜色但是实际上和区间覆盖没有任何区别,
因此直接正常修改正常传修改标记即可。

进行一个小总结就是:当需要建立多棵线段树时,为了减小空间,就可以建立多颗动态开点线段树

#include
using namespace std;
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
const int maxn=4e5+50;
int n,m,cnt;
int dep[maxn],siz[maxn],son[maxn],fa[maxn],id[maxn],top[maxn];
int w[maxn<<2],wt[maxn<<2];
int R[maxn],L[maxn];
int ecnt=-1,head[maxn];
int sum[maxn<<2],tag[maxn<<2];
struct mint
{
	int nxt,v;	
}e[maxn<<1];
inline void addline(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].v=v;
	head[u]=ecnt;	
}

inline void pushup(int k)
{
	L[k]=L[lson];
	R[k]=R[rson];
	sum[k]=sum[lson]+sum[rson];
	if(R[lson]==L[rson] && sum[k]>1) --sum[k];	
}

inline void Add(int k,int l,int r,int v)
{
	tag[k]=v;
	L[k]=R[k]=v;
	sum[k]=1;	
}

inline void pushdown(int k,int l,int r)
{
	if(tag[k]==-1) return;
	Add(lson,l,mid,tag[k]);
	Add(rson,mid+1,r,tag[k]); 
	tag[k]=-1;	
}

int querysum(int k,int l,int r,int x,int y)
{
	if(x<=l && r<=y) return sum[k];
	int res=0;
	pushdown(k,l,r);
	if(x<=mid) res+=querysum(lson,l,mid,x,y);
	if(y>mid) res+=querysum(rson,mid+1,r,x,y);
	if(x<=mid && y>mid && R[lson]==L[rson]) --res;
	pushup(k);
	return res; 
}

void change(int k,int l,int r,int x,int y,int t)
{
	if(x<=l && r<=y)
	{
		Add(k,l,r,t);
		return;
	}
	pushdown(k,l,r);
	if(x<=mid) change(lson,l,mid,x,y,t);
	if(y>mid) change(rson,mid+1,r,x,y,t);
	pushup(k); 
}

void build(int k,int l,int r)
{
	if(l==r)
	{
		L[k]=R[k]=wt[l];
		sum[k]=1;
		return;
	}
	build(lson,l,mid);
	build(rson,mid+1,r);
	pushup(k);
}

int dotquery(int k,int l,int r,int x)
{
	if(l==r) return R[k];
	pushdown(k,l,r);
	if(x<=mid) return dotquery(lson,l,mid,x);
	else return dotquery(rson,mid+1,r,x);
	pushup(k);
}

void dfs1(int x,int f,int deeep)
{
	siz[x]=1;
	dep[x]=deeep;
	fa[x]=f;
	int maxson=-1;
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==f) continue;
		dfs1(v,x,deeep+1);
		siz[x]+=siz[v];
		if(siz[v]>maxson) son[x]=v,maxson=siz[v];
	}
}

void dfs2(int x,int topf)
{
	top[x]=topf;
	id[x]=++cnt;
	wt[cnt]=w[x];
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==fa[x] || v==son[x]) continue;
		dfs2(v,v);
	}
}

void changepath(int x,int y,int t)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		change(1,1,n,id[top[x]],id[x],t);
		x=fa[top[x]];
	}
	if(dep[x]<dep[y]) swap(x,y);
	change(1,1,n,id[y],id[x],t);
}

int qpath(int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res+=querysum(1,1,n,id[top[x]],id[x]);
		if(dotquery(1,1,n,id[top[x]])==dotquery(1,1,n,id[fa[top[x]]])) --res;
		x=fa[top[x]];
	}
	if(dep[x]<dep[y]) swap(x,y);
	res+=querysum(1,1,n,id[y],id[x]);
	return res;
}

int main()
{
	memset(tag,-1,sizeof(tag));
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&w[i]);
	for(int i=1;i<=n-1;++i)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		addline(u,v);
		addline(v,u);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=m;++i)
	{
		char a;
		cin>>a;
		if(a=='C')
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			changepath(x,y,z);
		}
		else 
		{
			int x,y;
			scanf("%d%d",&x,&y);
			printf("%d\n",qpath(x,y));	
		}
	} 
	return 0;
}

T4:洛谷P3313 [SDOI2014]旅行 & YBTOJ-D. 旅行留宿

这道题肯定要对每一种不同宗教都建一颗线段树,因此空间复杂度肯定过不去。
所以可以采取对每一种宗教建一颗动态开点线段树即可。
这里的动态开点线段树只要完成单点修改和区间求和即可。
这里不对动态开点线段树进行过多的叙述。
想了解的话在csdn搜索

#include
using namespace std;
const int maxn=1e5+50;
int dep[maxn],fa[maxn],siz[maxn],id[maxn],cnt,top[maxn],son[maxn];
int wt[maxn],tot;
int rt[maxn];
int n,m;
int head[maxn],ecnt=-1;
struct mint
{
	int nxt,v;
}e[maxn<<1];
inline void addline(int u,int v)
{
	e[++ecnt].nxt=head[u];
	e[ecnt].v=v;
	head[u]=ecnt;	
}

struct dot
{
	int val,col;
}c[maxn];

struct tree
{
	int ls,rs,sum,maxx;	
}tr[maxn<<5];

void dfs1(int x,int f,int deeep)
{
	dep[x]=deeep;
	siz[x]=1;
	fa[x]=f;
	int maxson=-1;
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(v==f) continue;
		dfs1(v,x,deeep+1);
		siz[x]+=siz[v];
		if(siz[v]>maxson) son[x]=v,maxson=siz[v];
	}
}

void dfs2(int x,int topf)
{
	top[x]=topf;
	id[x]=++cnt;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int v=e[i].v;
		if(fa[x]==v || v==son[x]) continue;
		dfs2(v,v);	
	}
}

void pushup(int k,int ls,int rs)
{
	tr[k].sum=tr[ls].sum+tr[rs].sum;
	tr[k].maxx=max(tr[ls].maxx,tr[rs].maxx);	
}

void insert(int &k,int l,int r,int x,int v)
{
	if(!k) k=++tot;
	if(l==r)
	{
		tr[k].sum=tr[k].maxx=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) insert(tr[k].ls,l,mid,x,v);
	else insert(tr[k].rs,mid+1,r,x,v);
	pushup(k,tr[k].ls,tr[k].rs);
}

void del(int k,int l,int r,int x)
{
	if(!k) return;
	if(l==r)
	{
		tr[k].sum=tr[k].maxx=0;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) del(tr[k].ls,l,mid,x);
	else del(tr[k].rs,mid+1,r,x);
	pushup(k,tr[k].ls,tr[k].rs);
}

void update(int &k,int l,int r,int x,int v)
{
	if(!k) k=++tot;
	if(l==r)
	{
		tr[k].sum=tr[k].maxx=v;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) update(tr[k].ls,l,mid,x,v);
	else update(tr[k].rs,mid+1,r,x,v);
	pushup(k,tr[k].ls,tr[k].rs);
}

int querysum(int k,int l,int r,int x,int y)
{
	if(!k) return 0;
	if(x<=l && r<=y) return tr[k].sum;
	int mid=(l+r)>>1;
	int res=0;
	if(x<=mid) res+=querysum(tr[k].ls,l,mid,x,y);
	if(y>mid) res+=querysum(tr[k].rs,mid+1,r,x,y);
	return res;		
}

int querymax(int k,int l,int r,int x,int y)
{
	if(!k) return 0;
	if(x<=l && r<=y) return tr[k].maxx;
	int mid=(l+r)>>1;
	int res=-1;
	if(x<=mid) res=max(res,querymax(tr[k].ls,l,mid,x,y));
	if(y>mid) res=max(res,querymax(tr[k].rs,mid+1,r,x,y));
	return res;
}

int qpath_sum(int p,int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res+=querysum(p,1,n,id[top[x]],id[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res+=querysum(p,1,n,id[x],id[y]);
	return res;
}

int qpath_max(int p,int x,int y)
{
	int res=-1;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=max(res,querymax(p,1,n,id[top[x]],id[x]));
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res=max(res,querymax(p,1,n,id[x],id[y]));
	return res;
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d%d",&c[i].val,&c[i].col);
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y); 
		addline(x,y);
		addline(y,x);
	}
	fa[1]=1;
	dfs1(1,0,1);
	dfs2(1,1);
	for(int i = 1;i <= n;i++) insert(rt[c[i].col],1,n,id[i],c[i].val);
	while(m--)
	{
		char s[10];
		cin>>s;
		int x,y;
		scanf("%d%d",&x,&y);
		if(s[0]=='Q')
		{
			if(s[1]=='S') printf("%d\n",qpath_sum(rt[c[x].col],x,y));
			else printf("%d\n",qpath_max(rt[c[x].col],x,y));
		}
		else
		{
			if(s[1]=='C')
			{
				del(rt[c[x].col],1,n,id[x]);
				c[x].col = y;
				update(rt[c[x].col],1,n,id[x],c[x].val);
			}
			else
			{
				c[x].val = y;
				del(rt[c[x].col],1,n,id[x]);
				update(rt[c[x].col],1,n,id[x],c[x].val);
			}
		}
	}
	return 0;
}

四、后记:

这次的总结并不完善,会有后续博客进行补充。
看到标题的(一)应该就能想到了
后续的补充会包括更多的重链剖分难题,及长链剖分和实链剖分的内容。
BYE~

你可能感兴趣的:(图论,模板,算法,图论,C++,树链剖分,树上问题)