树链剖分个人题目总结

首先,想要学树链剖分基础的朋友们很抱歉请换篇博客看,,,这是个题目总结
//////////////////////////////////
本蒟蒻求不被大佬嘲讽。。。。
//////////////////////////////////
树剖啊啊,记得高一时因“运输计划”一题需要此算法,还不会线段树的我便去网上看。。。嗯嗯。。结果可想而知。。从此有了心理阴影。。
其实树剖没那么恶心,学完以后才发现,其实树剖是很简单的。。。

这里不再冗余地讲怎样做树剖,而是总结本蒟蒻所见的树剖题目。
 bzoj2836
题意:一棵树支持两种操作:1.将它的子树上所有节点值+v;2.询问u到v上所有点的权值和;
要点:
1.怎么维护子树的修改?
要通过dfs序维护。我们知道在树剖的dfs2中会通过dfs序来维护一个节点在线段树中所对应的位置,很明显,u的dfs序与遍历完子树回到u时最后一个dfs序刚好囊括了u和它的子树们在线段树中的位置,所以记录一个点的dfs前序与后序即可实现直接修改子树。
2.询问u到v
先询问u到lca(u,v)再询问v到lca(u,v),最后把lca(u,v)的值减去即可
这明显是道一眼树剖题。。。。
#include
#include
#include
#include
#include
#pragma comment(linker, "/STACK:10240000000,10240000000")//手动开大栈区 
#define lson rt*2
#define rson rt*2+1
#define mid (l+r)/2
using namespace std;
const int N=100050;
typedef long long ll;
int head[N],to[N*2],next[N*2],fa[N][35],cnt;
void add(int f,int t)
{
	next[++cnt]=head[f];
	to[cnt]=t;
	head[f]=cnt;
}
//
int n,q,a,b,c;
ll d;
//
int son[N],size[N],dep[N];
void dfs1(int rt)
{ 
	size[rt]=1;
	int flag=0;
	for(int i=head[rt];i!=-1;i=next[i])
	{
		dep[to[i]]=dep[rt]+1;
		dfs1(to[i]);
		size[rt]+=size[to[i]];
		if(size[to[i]]>flag)
			flag=size[to[i]],son[rt]=to[i];
	}
}
int zhe[N],fan[N],mx[N],top[N],num;
void dfs2(int rt,int root)
{
	top[rt]=root;
	zhe[rt]=++num;
	fan[num]=rt;
	if(son[rt])
		dfs2(son[rt],root);
	for(int i=head[rt];i!=-1;i=next[i])
		if(to[i]!=son[rt])
			dfs2(to[i],to[i]);
	mx[rt]=num;
}
//
void init()
{
	for(int j=1;j<=30;j++)
		for(int i=1;i<=n;i++)
			if(fa[i][j-1]!=0)
				fa[i][j]=fa[fa[i][j-1]][j-1];
}
int er[31]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824};
int lca(int u,int v)
{
	if(dep[u]=0;i--)
	{
		if(x>=er[i])
			u=fa[u][i],x-=er[i];
	}
	if(u==v)
		return u;
	for(int i=31;i>=0;i--)
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
struct node
{
	ll sum,flag;
}tree[4*N];
void pushup(int rt)
{
	tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void pushdown(int rt,int l,int r)
{
	if(!tree[rt].flag)
		return ;
	tree[lson].sum+=(mid-l+1)*tree[rt].flag;
	tree[rson].sum+=(r-mid)*tree[rt].flag;
	tree[lson].flag+=tree[rt].flag;
	tree[rson].flag+=tree[rt].flag;
	tree[rt].flag=0;
}
void ins(int rt,int l,int r,int L,int R,ll x)
{
	if(l==L&&r==R)
	{
		tree[rt].sum+=(r-l+1)*x;
		tree[rt].flag+=x;
		return ;
	}
	pushdown(rt,l,r);
	if(mid>=R)
		ins(lson,l,mid,L,R,x);
	else if(middep[aim])
			ins(1,1,n,zhe[top[u]],zhe[u],x),u=fa[top[u]][0];
		else
			ins(1,1,n,zhe[aim],zhe[u],x),u=aim,cnt1++;
	}
	while(v!=aim)
	{
		if(dep[top[v]]>dep[aim])
			ins(1,1,n,zhe[top[v]],zhe[v],x),v=fa[top[v]][0];
		else
			ins(1,1,n,zhe[aim],zhe[v],x),v=aim,cnt1++;
	}
	if(cnt1==0)
		ins(1,1,n,zhe[aim],zhe[aim],x);
	else if(cnt==2)
		ins(1,1,n,zhe[aim],zhe[aim],-x);
}
ll query(int rt,int l,int r,int L,int R)
{
	if(l==L&&r==R)
		return tree[rt].sum;
	pushdown(rt,l,r);
	if(mid>=R)
		return query(lson,l,mid,L,R);
	else if(mid

bzoj1984
题意:一棵树,点上无权而边上有值,现有四种操作:
将一个边的值修改,将一个点到另一个点上所有边修改,将一个点到另一个上所有边加上一个值,询问一个点到另一个点上所有边中的最大值。
要点:
1.是边权而非点权
因为这是棵树,所以在确定了根以后我们可以将一个边的值赋到指向的儿子节点上,便可以做树剖了。
2.怎么又改值又加值
若一个值被改,那么之前所加的值都会无效,所以,线段树维护两个延迟标记,标记1表示它应该先被改成什么值,标记2表示完成1后它应该加上多少值,pushdown时首先看标记1是否为-1(即存不存在改值操作),若存在,那么先将左右子节点值直接改,并将它们的标记1更改为当前节点的标记1,然后清零它们的标记2。之后给两个子节点加上当前子节点的标记2值,然后给两个标记2也加上自己的值。
3.细节
注意在边权化点权的情况下,要注意在修改时将lca(u,v)避过去。
#include
#include
#include
#include
#include
#define lson rt*2
#define rson rt*2+1
#define mid (l+r)/2
using namespace std;
const int N=100050;
int n;
int head[N],to[N*2],next[2*N],v2[2*N],v1[2*N],id[2*N],fan[N],cnt;
void add(int f,int t,int val,int hao)
{
	to[++cnt]=t;
	id[cnt]=hao;
	v1[cnt]=val;
	next[cnt]=head[f];
	head[f]=cnt;
}
int a,b,c;
int size[N],son[N];
int fa[N][25];
int s[25],dep[N];
void dfs1(int x)
{
	dep[x]=dep[fa[x][0]]+1;
	size[x]=1;
	for(int i=1;i<=16;i++)
	{
		if(s[i]>=dep[x])
			break;
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}
	for(int i=head[x];i!=-1;i=next[i])
	{
		if(to[i]==fa[x][0])
			continue;
		fa[to[i]][0]=x;
		fan[id[i]]=to[i];
		v2[to[i]]=v1[i];
		dfs1(to[i]);
		size[x]+=size[to[i]];
		if(size[to[i]]>size[son[x]])
			son[x]=to[i];
	}
}
int zhe[N],top[N],num;
void dfs2(int x,int root)
{
	zhe[x]=++num;
	top[x]=root;
	if(son[x])
		dfs2(son[x],root);
	for(int i=head[x];i!=-1;i=next[i])
		if(to[i]!=son[x]&&to[i]!=fa[x][0])
			dfs2(to[i],to[i]);
}
int lca(int u,int v)
{
	if(dep[u]=0;i--)
		if(cha&s[i])
			u=fa[u][i];
	if(u==v)
		return v;
	for(int i=16;i>=0;i--)
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
struct node
{
	int mx,flag,flah;
}tree[N*8];
void pushup(int rt)
{
	tree[rt].mx=max(tree[lson].mx,tree[rson].mx);
}
void pushdown(int rt,int l,int r)
{
	if(!tree[rt].flag&&tree[rt].flah==-1)
		return;
	if(l==r)
		return ;
	if(tree[rt].flah!=-1)
	{
		tree[lson].flag=tree[rson].flag=0;
		tree[lson].flah=tree[rson].flah=tree[rt].flah;
		tree[lson].mx=tree[rson].mx=tree[rt].flah;
		tree[rt].flah=-1;
	}
	tree[lson].flag+=tree[rt].flag;
	tree[rson].flag+=tree[rt].flag;
	tree[lson].mx+=tree[rt].flag;
	tree[rson].mx+=tree[rt].flag;
	tree[rt].flag=0;
}
void ins1(int rt,int l,int r,int pos,int val)
{
	pushdown(rt,l,r);
	if(l==r)
	{
		tree[rt].mx=val;
		return ;
	}
	if(mid=pos)
		ins1(lson,l,mid,pos,val);
	pushup(rt);
}
void ins2(int rt,int l,int r,int L,int R,int val)
{
	pushdown(rt,l,r);
	if(l==L&&r==R)
	{
		tree[rt].flag+=val;
		tree[rt].mx+=val;
		return ;
	}
	if(mid>=R)
		ins2(lson,l,mid,L,R,val);
	else	if(mid=R)
		ins3(lson,l,mid,L,R,val);
	else	if(mid=R)
		return query(lson,l,mid,L,R);
	else	if(mid

bzoj4034
题意:一棵树,修改子树,修改单点,询问点到根和。
没什么要点。。。实在太裸了。。。
#include
#include
#include
#include
#include
#define lson rt*2
#define rson rt*2+1
#define mid (l+r)/2
using namespace std;
const int N=100050;
typedef long long ll;
int m,n;
int head[N],next[2*N],to[2*N],cnt;
void add(int f,int t)
{
	to[++cnt]=t;
	next[cnt]=head[f];
	head[f]=cnt;
}
ll v[N];
int a,b;
ll c;
struct node
{
	ll sum,flag;
}tree[N*4];
ll size[N];
int son[N],fa[N];
void dfs1(int rt,int last)
{
	ll flag=0;
	size[rt]=1;
	for(int i=head[rt];i!=-1;i=next[i])
	{
		if(to[i]==last)
			continue;
		fa[to[i]]=rt;
		dfs1(to[i],rt);
		size[rt]+=size[to[i]];
		if(size[to[i]]>flag)
			flag=size[to[i]],son[rt]=to[i];
	}
}
int num,fan[N],zhe[N],top[N],mx[N];
ll vl[N];
void dfs2(int rt,int root)
{
	zhe[rt]=++num;
	fan[num]=rt;
	vl[num]=v[rt];
	top[rt]=root;
	if(son[rt])
		dfs2(son[rt],root);
	for(int i=head[rt];i!=-1;i=next[i])
		if(to[i]!=fa[rt]&&to[i]!=son[rt])
			dfs2(to[i],to[i]);
	mx[rt]=num;
}
void pushup(int rt)
{
	tree[rt].sum=tree[lson].sum+tree[rson].sum;
}
void pushdown(int rt,int l,int r)
{
	tree[lson].sum+=(mid-l+1)*tree[rt].flag;
	tree[rson].sum+=(r-mid)*tree[rt].flag;
	tree[lson].flag+=tree[rt].flag;
	tree[rson].flag+=tree[rt].flag;
	tree[rt].flag=0;
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		tree[rt].sum=vl[l];
		return ;
	}
	build(lson,l,mid);
	build(rson,mid+1,r);
	pushup(rt);
}
void ins(int rt,int l,int r,int L,int R,ll x)
{
	if(l==L&&r==R)
	{
		tree[rt].sum+=(r-l+1)*x;
		tree[rt].flag+=x;
		return ;
	}
	pushdown(rt,l,r);
	if(mid=R)
		ins(lson,l,mid,L,R,x);
	else
		ins(lson,l,mid,L,mid,x),ins(rson,mid+1,r,mid+1,R,x);
	pushup(rt);
}
ll query(int rt,int l,int r,int L,int R)
{
	if(l==L&&r==R)
		return tree[rt].sum;
	pushdown(rt,l,r);
	if(R<=mid)
		return query(lson,l,mid,L,R);
	else if(mid

bzoj3626
题意:给一棵树。每次询问区间[l,r]这些点与z的sigma(dep[lca(i,z)]),l<=i<=r.
这个题实在是太吼了!

清华大佬gconeice的题解:

显然,暴力求解的复杂度是无法承受的。
考虑这样的一种暴力,我们把 z 到根上的点全部打标记,对于 l 到 r 之间的点,向上搜索到第一个有标记的点求出它的深度统计答案。观察到,深度其实就是上面有几个已标记了的点(包括自身)。所以,我们不妨把 z 到根的路径上的点全部 +1,对于 l 到 r 之间的点询问他们到根路径上的点权和。仔细观察上面的暴力不难发现,实际上这个操作具有叠加性,且可逆。也就是说我们可以对于 l 到 r 之间的点 i,将 i 到根的路径上的点全部 +1, 转而询问 z 到根的路径上的点(包括自身)的权值和就是这个询问的答案。把询问差分下,也就是用 [1, r] − [1, l − 1] 来计算答案,那么现在我们就有一个明显的解法。从 0 到 n − 1 依次插入点 i,即将 i 到根的路径上的点全部+1。离线询问答案即可。我们现在需要一个数据结构来维护路径加和路径求和,显然树链剖分或LCT 均可以完成这个任务。树链剖分的复杂度为 O((n + q)· log n · log n),LCT的复杂度为 O((n + q)· log n),均可以完成任务。至此,题目已经被我们完美解决。(摘自 hzwer.com)

实在是太妙了啊啊啊在下不得不orz

#include
#include
#include
#include
#include
#include
#define lson rt*2
#define rson rt*2+1
#define mid (l+r)/2
using namespace std;
const int N=50050;
const int mo=201314;
int ans[N];
int n,q,num;
int l,r,z;
struct state
{
	int loc,zz;
};
vector jan[N],jia[N];
int head[N],to[N],next[N],cnt;
void add(int f,int t)
{
	to[++cnt]=t;
	next[cnt]=head[f];
	head[f]=cnt;
}
int fa[N];
int size[N],son[N],zhe[N],dui[N],top[N];
void dfs1(int x)
{
	size[x]=1;
	int flag=0;
	for(int i=head[x];i!=-1;i=next[i])
	{
		dfs1(to[i]);
		size[x]+=size[to[i]];
		if(size[to[i]]>flag)
		{
			flag=size[to[i]];
			son[x]=to[i];
		}
	}
}
void dfs2(int x,int root)
{
	zhe[x]=++num;
	dui[num]=x;
	top[x]=root;
	if(son[x])
		dfs2(son[x],root);
	for(int i=head[x];i!=-1;i=next[i])
		if(to[i]!=son[x])
			dfs2(to[i],to[i]);
}
struct node
{
	int sum;
	int flag;
}tree[N*4];
void pushup(int rt)
{
	tree[rt].sum=(tree[lson].sum+tree[rson].sum)%mo;
}
void pushdown(int rt,int l,int r)
{
	if(!tree[rt].flag)
		return ;
	tree[lson].flag+=tree[rt].flag;
	tree[rson].flag+=tree[rt].flag;
	tree[lson].sum=(tree[lson].sum+(tree[rt].flag*(mid-l+1))%mo)%mo;
	tree[rson].sum=(tree[rson].sum+(tree[rt].flag*(r-mid))%mo)%mo;
	tree[rt].flag=0;
}
void ins(int rt,int l,int r,int L,int R)
{
	if(l==L&&r==R)
	{
		tree[rt].sum=(tree[rt].sum+(r-l+1))%mo;
		tree[rt].flag+=1;
		return ;
	}
	pushdown(rt,l,r);
	if(mid>=R)
		ins(lson,l,mid,L,R);
	else if(mid=R)
		return query(lson,l,mid,L,R)%mo;
	else if(mid

bzoj3531

题意:

一棵树,每个树有一种颜色和一个权值,现在有四种操作:

1.改变一个节点的权值

2.改变一个节点的颜色

3.询问从u到v(保证u,v颜色相同)这条路上与uv颜色相同的点的权值总和

4.询问从u到v(也保证u,v颜色相同)这条路上与uv颜色相同的点的权值最大值

要点:

1.怎么判断是不是颜色相同?

我学会了特殊的开多个线段树的技巧,我会假装四处看风景!

好吧不是什么特殊的,就是这个题需要为每个宗教开一个线段树,而我终于掌握了这种技巧。。。

#include
#include
#include
#include
#include
#define mid (l+r)/2
using namespace std;
const int N=100050;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m,a,b;
int head[N],to[2*N],next[2*N],cnt;
void add(int f,int t)
{
	to[++cnt]=t;
	next[cnt]=head[f];
	head[f]=cnt;
}
int s[N];
int T[N],w[N];
int fa[N][25];
int son[N],size[N];
int dep[N];
void dfs1(int x)
{
	dep[x]=dep[fa[x][0]]+1;
	for(int i=1;i<=16;i++)
	{
		if(s[i]>dep[x])
			break;
		fa[x][i]=fa[fa[x][i-1]][i-1];
	}
	size[x]=1;
	int flag=0;
	for(int i=head[x];i!=-1;i=next[i])
	{
		if(to[i]==fa[x][0])
			continue;
		fa[to[i]][0]=x;	
		dfs1(to[i]);
		size[x]+=size[to[i]];
		if(size[to[i]]>flag)
			flag=size[to[i]],son[x]=to[i];
	}
}
int zhe[N],top[N],num;
void dfs2(int x,int root)
{
	zhe[x]=++num;
	top[x]=root;
	if(son[x])
		dfs2(son[x],root);
	for(int i=head[x];i!=-1;i=next[i])
		if(to[i]!=son[x]&&to[i]!=fa[x][0])
			dfs2(to[i],to[i]);
}
int lca(int u,int v)
{
	if(dep[u]=0;i--)
		if(cha&s[i])
			u=fa[u][i];
	if(u==v)
		return u;
	for(int i=16;i>=0;i--)
		if(fa[u][i]!=fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
struct node
{
	int sum,mx,ls,rs;
}tree[100*N];
void pushup(int rt)
{
	tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;
	tree[rt].mx=max(tree[tree[rt].ls].mx,tree[tree[rt].rs].mx);
}
int tot;
int root[N];
void ins(int &rt,int l,int r,int pos,int val)
{
	if(!rt)
		rt=++tot;
	if(l==r)
	{
		tree[rt].mx=tree[rt].sum=val;
		return ;
	}
	if(pos<=mid)
		ins(tree[rt].ls,l,mid,pos,val);
	else
		ins(tree[rt].rs,mid+1,r,pos,val);
	pushup(rt);
}
int querysum(int rt,int l,int r,int L,int R)
{
	if(!rt)	return 0;
	if(l==L&&r==R)
		return tree[rt].sum;
	if(mid>=R)
		return querysum(tree[rt].ls,l,mid,L,R);
	else if(mid=R)
		return querymax(tree[rt].ls,l,mid,L,R);
	else if(mid


你可能感兴趣的:(树链剖分)