首页 > 其他分享 >树剖练习题题解总和(动态更新)

树剖练习题题解总和(动态更新)

时间:2023-02-24 14:12:25浏览次数:58  
标签:练习题 nl 树剖 int 题解 mid dep return id

这篇博客的练习题题解

1、P3384 【模板】轻重链剖分/树链剖分

模板题,详见

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,m,r,p,t[100005],fa[100005],dep[100005],son[100005],id[100005],w[100005],tp[100005],name,f[400005],laz[400005],nl,nr,k,sum[100005];
vector<int>a[100005];
int dfs1(int x,int fath)
{
	fa[x]=fath;
	dep[x]=dep[fath]+1;
	int len=a[x].size(),maxn=-1,ma=0;
	sum[x]=1;
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fath)continue;
		int num=dfs1(a[x][i],x);
		if(num>maxn)
		{
			maxn=num;
			ma=a[x][i];
		}
		sum[x]+=num;
	}
	son[x]=ma;
	return sum[x];
}
void dfs2(int x,int topp)
{
	id[x]=++name;
	w[name]=t[x];
	tp[x]=topp;
	if(!son[x])return;
	dfs2(son[x],topp);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==son[x]||a[x][i]==fa[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=w[l]%p;
		return;
	}
	int mid=(l+r)>>1;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	f[x]=f[ls(x)]+f[rs(x)];
}
void fix(int x,int l,int r,int kt)
{
	f[x]+=(r-l+1)*kt,f[x]%=p;
	laz[x]+=kt,laz[x]%=p;
}
void pushdown(int x,int l,int r)
{
	int mid=(l+r)>>1;
	fix(ls(x),l,mid,laz[x]);
	fix(rs(x),mid+1,r,laz[x]);
	laz[x]=0;
}
int search(int x,int l,int r)
{
	if(l>=nl&&nr>=r)return f[x];
	int mid=(l+r)>>1,num=0;
	pushdown(x,l,r);
	if(mid>=nl)num+=search(ls(x),l,mid),num%=p;
	if(nr>mid)num+=search(rs(x),mid+1,r),num%=p;
	return num;
}
void update(int x,int l,int r)
{
	if(l>=nl&&nr>=r)
	{
		f[x]+=(r-l+1)*k,f[x]%=p;
		laz[x]+=k,laz[x]%=p;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	if(nr>mid)update(rs(x),mid+1,r);
	f[x]=f[ls(x)]+f[rs(x)],f[x]%=p;
}
void update_jump(int x,int y)
{
	while(tp[x]!=tp[y])
	{
		if(dep[tp[x]]<dep[tp[y]])swap(x,y);
		nr=id[x],nl=id[tp[x]];
		update(1,1,n);
		x=fa[tp[x]];
	}
	if(id[x]>id[y])swap(x,y);
	nl=id[x],nr=id[y];
	update(1,1,n);
}
int search_jump(int x,int y)
{
	int num=0;
	while(tp[x]!=tp[y])
	{
		if(dep[tp[x]]<dep[tp[y]])swap(x,y);
		nl=id[tp[x]],nr=id[x];
		num+=search(1,1,n),num%=p;
		x=fa[tp[x]];
	}
	if(id[x]>id[y])swap(x,y);
	nl=id[x],nr=id[y];
	return (num+search(1,1,n))%p;
}
void update_tree(int x)
{
	nl=id[x],nr=id[x]+sum[x]-1;
	update(1,1,n);
}
int search_tree(int x)
{
	nl=id[x],nr=id[x]+sum[x]-1; 
	return search(1,1,n);
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&r,&p);
	for(int i=1;i<=n;i++)scanf("%d",&t[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int T;
		scanf("%d",&T);
		if(T==1)
		{
			int x,y;
			scanf("%d%d%d",&x,&y,&k);
			k%=p;
			update_jump(x,y);
		}
		if(T==2)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			int ans=search_jump(x,y);
			printf("%d\n",ans);
		}
		if(T==3)
		{
			int x;
			scanf("%d%d",&x,&k);
			k%=p;
			update_tree(x);
		}
		if(T==4)
		{
			int x;
			scanf("%d",&x);
			int ans=search_tree(x);
			printf("%d\n",ans);
		}
	}
	return 0;
}

2、P3313 [SDOI2014]旅行(较简单的变式题)

因为有宗教的存在,传统的线段树+树链剖分已无法解决此题,必须要想办法处理这个问题。

实际上也挺好想,就建立 \(10^5\) 个线段树,每棵线段树对应一个宗教。

操作1:将之前宗教所处的线段树对应的点赋为 \(0\),再添加到新宗教的线段树即可。

操作2:改值即可。

操作3:树剖求此线段树的和。

操作4:树剖求此线段树的最大值。

动态开点即可。

#include<iostream>
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
const int N=1e5+5;
int n,q,w[N],c[N],fa[N],dep[N],topp[N],id[N],cnt,siz[N],son[N],rt[N],nl,nr,k;
vector<int>a[N];
struct node
{
	int num,maxn,ls,rs;
}f[20*N];
int dfs1(int x,int fath)
{
	dep[x]=dep[fath]+1;
	fa[x]=fath;
	siz[x]=1;
	int len=a[x].size(),maxn=0;
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fath)continue;
		int num=dfs1(a[x][i],x);
		if(num>maxn)maxn=num,son[x]=a[x][i];
		siz[x]+=num;
	}
	return siz[x];
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	id[x]=++cnt;
	if(son[x])dfs2(son[x],tp);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa[x]||a[x][i]==son[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
void pushup(int x)
{
	f[x].num=f[f[x].ls].num+f[f[x].rs].num;
	f[x].maxn=max(f[f[x].ls].maxn,f[f[x].rs].maxn);
}
void update(int &x,int l,int r)
{
	if(!x)x=++cnt;
	if(l==r)
	{
		f[x].num=f[x].maxn=k;
		return;
	}
	int mid=(l+r)>>1;
	if(mid>=nl)update(f[x].ls,l,mid);
	else update(f[x].rs,mid+1,r);
	pushup(x);
}
int search1(int x,int l,int r)
{
	if(!x)return 0;
	if(l>=nl&&r<=nr)return f[x].num;
	int mid=(l+r)>>1;
	int sum=0;
	if(mid>=nl)sum+=search1(f[x].ls,l,mid);
	if(mid<nr)sum+=search1(f[x].rs,mid+1,r);
	return sum;
}
int search2(int x,int l,int r)
{
	if(!x)return 0;
	if(l>=nl&&r<=nr)return f[x].maxn;
	int mid=(l+r)>>1;
	int maxn=0;
	if(mid>=nl)maxn=max(maxn,search2(f[x].ls,l,mid));
	if(mid<nr)maxn=max(maxn,search2(f[x].rs,mid+1,r));
	return maxn;
}
int search_road_sum(int x,int y)
{
	int t=c[x],sum=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		sum+=search1(rt[t],1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	sum+=search1(rt[t],1,n);
	return sum;
}
int search_road_max(int x,int y)
{
	int t=c[x],maxn=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		maxn=max(maxn,search2(rt[t],1,n));
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	maxn=max(maxn,search2(rt[t],1,n));
	return maxn;
}
signed main()
{
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&w[i],&c[i]);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%lld%lld",&u,&v);
		a[u].push_back(v);
		a[v].push_back(u);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=n;i++)
	{
		nl=id[i],k=w[i];
		update(rt[c[i]],1,n);
	}
	for(int i=1;i<=q;i++)
	{
		char ch[3];
		scanf("%s",ch+1);
		int x,y;
		scanf("%lld%lld",&x,&y);
		if(ch[1]=='C'&&ch[2]=='C')
		{
			nl=id[x],k=0;
			update(rt[c[x]],1,n);
			c[x]=y,k=w[x];
			update(rt[c[x]],1,n);
		}
		if(ch[1]=='C'&&ch[2]=='W')
		{
			nl=id[x],k=y;
			update(rt[c[x]],1,n);
			w[x]=y;
		}
		if(ch[1]=='Q'&&ch[2]=='S')
		{
			int ans=search_road_sum(x,y);
			printf("%lld\n",ans);
		}
		if(ch[1]=='Q'&&ch[2]=='M')
		{
			int ans=search_road_max(x,y);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

3、P2590 [ZJOI2008]树的统计(练习题)

模板练习,不懂见开始的那个博客。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=3e4+5;
int n,dep[N],son[N],fa[N],topp[N],id[N],t[N],w[N],f[4*N],m[4*N],cnt,nl,nr,k;
vector<int>a[N];
int dfs1(int x,int fath)
{
	fa[x]=fath;
	dep[x]=dep[fath]+1;
	int maxn=0,sum=1;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fath)continue;
		int num=dfs1(a[x][i],x);
		if(num>maxn)maxn=num,son[x]=a[x][i];
		sum+=num;
	}
	return sum;
}
void dfs2(int x,int tp)
{
	id[x]=++cnt;
	w[cnt]=t[x];
	topp[x]=tp;
	int len=a[x].size();
	if(son[x])dfs2(son[x],tp);
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa[x]||a[x][i]==son[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=f[ls(x)]+f[rs(x)];
	m[x]=max(m[ls(x)],m[rs(x)]);
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=m[x]=w[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	pushup(x);
}
void update(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=m[x]=k;
		return;
	}
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	else update(rs(x),mid+1,r);
	pushup(x);
}
int search1(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return f[x];
	int mid=(l+r)>>1,sum=0;
	if(mid>=nl)sum+=search1(ls(x),l,mid);
	if(mid<nr)sum+=search1(rs(x),mid+1,r);
	return sum;
}
int search2(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return m[x];
	int mid=(l+r)>>1,maxn=-30005;
	if(mid>=nl)maxn=max(maxn,search2(ls(x),l,mid));
	if(mid<nr)maxn=max(maxn,search2(rs(x),mid+1,r));
	return maxn;
}
int search1_road(int x,int y)
{
	int sum=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		sum+=search1(1,1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	sum+=search1(1,1,n);
	return sum;
}
int search2_road(int x,int y)
{
	int maxn=-30005;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		maxn=max(maxn,search2(1,1,n));
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	maxn=max(maxn,search2(1,1,n));
	return maxn;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		a[u].push_back(v);
		a[v].push_back(u);
	}
	for(int i=1;i<=n;i++)scanf("%d",&t[i]);
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		char opt[10];
		scanf("%s",opt);
		if(opt[0]=='C')
		{
			int x;
			scanf("%d%d",&x,&k);
			nl=id[x];
			update(1,1,n);
		}
		else if(opt[1]=='M')
		{
			int x,y;
			scanf("%d%d",&x,&y);
			int ans=search2_road(x,y);
			printf("%d\n",ans); 
		}
		else
		{
			int x,y;
			scanf("%d%d",&x,&y);
			int ans=search1_road(x,y);
			printf("%d\n",ans);
		}
		
	}
	return 0;
}

4、P1505 [国家集训队]旅游(练习题)

为什么一道板题要搞得这么花里胡哨啊。

边权转换为点权,对于每一条边的权值,转换到儿子上面即可,根节点不进行遍历。

取相反数也很简单,区间和取反,区间最小值为区间最大值的相反数,区间最大值为区间最小值的相反数。

注意细节,这题细节特别多,如求路径时两点的 LCA 是不能计算的,因为 LCA 与父亲的路径并没有更新,还有特判根节点是不能取的,具体看代码。(有亿点长,应该是这个系列第二长的了)

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=2e5+5;
int n,m,t[N],dep[N],fa[N],siz[N],son[N],cnt,w[N],id[N],topp[N],f[4*N],laz[4*N],ma[4*N],mi[4*N],nl,nr,k,p[N];
struct node
{
	int name,to,data;
};
vector<node>a[N];
int dfs1(int x,int fath)
{
	dep[x]=dep[fath]+1;
	fa[x]=fath;
	siz[x]=1;
	int len=a[x].size(),maxn=0;
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fath)continue;
		p[a[x][i].name]=a[x][i].to;
		t[a[x][i].to]=a[x][i].data;
		int num=dfs1(a[x][i].to,x);
		if(num>maxn)maxn=num,son[x]=a[x][i].to;
		siz[x]+=num;
	}
	return siz[x];
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	id[x]=++cnt;
	w[cnt]=t[x];
	if(son[x])dfs2(son[x],tp);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i].to==fa[x]||a[x][i].to==son[x])continue;
		dfs2(a[x][i].to,a[x][i].to);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=f[ls(x)]+f[rs(x)];
	ma[x]=max(ma[ls(x)],ma[rs(x)]);
	mi[x]=min(mi[ls(x)],mi[rs(x)]);
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=mi[x]=ma[x]=w[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	pushup(x);
}
inline void fix(int x,int l,int r,int kt)
{
	f[x]=-f[x];
	int kp=mi[x];
	mi[x]=-ma[x];
	ma[x]=-kp;
	laz[x]^=1;
}
inline void pushdown(int x,int l,int r)
{
	if(!laz[x])return;
	int mid=(l+r)>>1;
	fix(ls(x),l,mid,laz[x]);
	fix(rs(x),mid+1,r,laz[x]);
	laz[x]=0;
}
void update(int x,int l,int r)
{
	if(l>=nl&&r<=nr)
	{
		f[x]=-f[x];
		int kp=mi[x];
		mi[x]=-ma[x];
		ma[x]=-kp;
		laz[x]^=1;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	if(mid<nr)update(rs(x),mid+1,r);
	pushup(x);
}
void update2(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=mi[x]=ma[x]=k;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update2(ls(x),l,mid);
	else update2(rs(x),mid+1,r);
	pushup(x);
}
int search(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return f[x];
	pushdown(x,l,r);
	int mid=(l+r)>>1,sum=0;
	if(mid>=nl)sum+=search(ls(x),l,mid);
	if(mid<nr)sum+=search(rs(x),mid+1,r);
	return sum;
}
int search2(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return ma[x];
	pushdown(x,l,r);
	int mid=(l+r)>>1,maxn=-1e9;
	if(mid>=nl)maxn=max(maxn,search2(ls(x),l,mid));
	if(mid<nr)maxn=max(maxn,search2(rs(x),mid+1,r));
	return maxn;
}
int search3(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return mi[x];
	pushdown(x,l,r);
	int mid=(l+r)>>1,minn=1e9;
	if(mid>=nl)minn=min(minn,search3(ls(x),l,mid));
	if(mid<nr)minn=min(minn,search3(rs(x),mid+1,r));
	return minn;
}
void update_point(int x)
{
	nl=id[p[x]];
	update2(1,2,n);
}
void update_road(int x,int y)
{
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		update(1,2,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x]+1,nr=id[y];
	if(nl<=nr)update(1,2,n);
}
int search_road(int x,int y)
{
	int sum=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		sum+=search(1,2,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x]+1,nr=id[y];
	if(nl<=nr)sum+=search(1,2,n);
	return sum;
}
int search2_road(int x,int y)
{
    int maxn=-1e9;
    while(topp[x]!=topp[y])
    {
        if(dep[topp[x]]<dep[topp[y]])swap(x,y);
        nl=id[topp[x]],nr=id[x];
        maxn=max(maxn,search2(1,2,n));
        x=fa[topp[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    nl=id[x]+1,nr=id[y];
    if(nl<=nr)maxn=max(maxn,search2(1,2,n));
    return maxn;
}
int search3_road(int x,int y)
{
    int minn=1e9;
    while(topp[x]!=topp[y])
    {
        if(dep[topp[x]]<dep[topp[y]])swap(x,y);
        nl=id[topp[x]],nr=id[x];
        minn=min(minn,search3(1,2,n));
        x=fa[topp[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    nl=id[x]+1,nr=id[y];
    if(nl<=nr)minn=min(minn,search3(1,2,n));
    return minn;
}
signed main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		u++,v++;
		a[u].push_back((node){i,v,w});
		a[v].push_back((node){i,u,w});
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,2,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		char opt[5];
		scanf("%s",opt+1);
		if(opt[1]=='S')
		{
			int x,y;
			scanf("%d%d",&x,&y);
			x++,y++;
			int ans=search_road(x,y);
			printf("%d\n",ans);
		}
		if(opt[1]=='N')
		{
			int x,y;
			scanf("%d%d",&x,&y);
			x++,y++;
			update_road(x,y);
		}
		if(opt[1]=='C')
		{
			int x;
			scanf("%d%d",&x,&k);
			update_point(x);
		}
		if(opt[1]=='M'&&opt[2]=='I')
		{
			int x,y;
			scanf("%d%d",&x,&y);
			x++,y++;
			int ans=search3_road(x,y);
			printf("%d\n",ans);
		}
		if(opt[1]=='M'&&opt[2]=='A')
		{
			int x,y;
			scanf("%d%d",&x,&y);
			x++,y++;
			int ans=search2_road(x,y);
			printf("%d\n",ans);
		}
	}
	return 0;
}

5、P2486 [SDOI2011]染色(较简单的变式题)

求树上颜色段的和,很明显树剖的部分不需要发生任何改变,仅仅改变线段树维护即可。

线段树多维护两个值,\(f_i\) 代表区间和个数,\(lc_i,rc_i\) 代表区间 \(i\) 最左边与最右边的颜色。

合并时判断合并两边颜色是否相同,若相同,则颜色段数量减一。

操作1:区间修改,打上懒标记即可。

操作2:在跳时直接可以提前判断链的顶端是否与顶端的父亲颜色相等,然后改变方案数,合并时就不用再减去方案数了。

本题要求对线段树掌握熟练。

#include<iostream>
#include<cstdio>
#include<vector>
#define N 100005
using namespace std;
int n,m,t[N],son[N],fa[N],dep[N],to[N],id[N],w[N],cnt,f[4*N],laz[4*N],lc[4*N],rc[4*N],nl,nr,k;
vector<int>a[N];
int dfs1(int x,int fath)
{
	fa[x]=fath;
	dep[x]=dep[fa[x]]+1;
	int maxn=-1,len=a[x].size(),num=1;
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa[x])continue;
		int ans=dfs1(a[x][i],x);
		num+=ans;
		if(ans>maxn)son[x]=a[x][i],maxn=ans;
	}
	return num;
}
void dfs2(int x,int topp)
{
	to[x]=topp;
	id[x]=++cnt;
	w[cnt]=t[x];
	if(!son[x])return;
	dfs2(son[x],to[x]);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa[x]||a[x][i]==son[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=f[ls(x)]+f[rs(x)];
	lc[x]=lc[ls(x)];
	rc[x]=rc[rs(x)];
	if(rc[ls(x)]==lc[rs(x)])f[x]--;
}
void build(int x,int l,int r)
{
	if(l==r)
	{
		f[x]=1;
		lc[x]=w[l];
		rc[x]=w[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	pushup(x);
}
void fix(int x,int kt)
{
	f[x]=1;
	lc[x]=rc[x]=kt;
	laz[x]=kt;
}
inline void pushdown(int x)
{
	if(laz[x]==0)return;
	fix(ls(x),laz[x]);
	fix(rs(x),laz[x]);
	laz[x]=0;
}
void update(int x,int l,int r)
{
	if(nl<=l&&r<=nr)
	{
		f[x]=1;
		lc[x]=rc[x]=laz[x]=k;
		return;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	if(mid<nr)update(rs(x),mid+1,r);
	pushup(x);
}
int search(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return f[x];
	pushdown(x);
	int mid=(l+r)>>1,num=0;
	if(mid>=nl)num+=search(ls(x),l,mid);
	if(mid<nr)num+=search(rs(x),mid+1,r);
	if(mid>=nl&&mid<nr&&rc[ls(x)]==lc[rs(x)])num--;
	return num;
}
int getting(int x,int l,int r)
{
	if(l==r)return lc[x];
	pushdown(x);
	int mid=(l+r)>>1;
	if(mid>=nl)return getting(ls(x),l,mid);
	return getting(rs(x),mid+1,r);
}
void update_jump(int x,int y)
{
	while(to[x]!=to[y])
	{
		if(dep[to[x]]<dep[to[y]])swap(x,y);
		nr=id[x],nl=id[to[x]];
		update(1,1,n);
		x=fa[to[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	update(1,1,n);
}
int search_jump(int x,int y)
{
	int num=0;
	while(to[x]!=to[y])
	{
		if(dep[to[x]]<dep[to[y]])swap(x,y);
		nl=id[to[x]],nr=id[x];
		num+=search(1,1,n);
		x=to[x];
		nl=id[x];
		int fi=getting(1,1,n);
		x=fa[x];
		nl=id[x];
		int se=getting(1,1,n);
		if(fi==se)num--;
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	num+=search(1,1,n);
	return num;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&t[i]);
	for(int i=1;i<n;i++)
	{
		int fi,se;
		scanf("%d%d",&fi,&se);
		a[fi].push_back(se);
		a[se].push_back(fi);
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		char T;
		cin>>T;
		int x,y;
		scanf("%d%d",&x,&y);
		if(T=='C')
		{
			scanf("%d",&k);
			update_jump(x,y);
		}
		else
		{
			int ans=search_jump(x,y);
			printf("%d\n",ans);
		}
	}
	return 0;
}

6、P3258 松鼠的新家(练习题)

每次对 \(fa_i\) 到 \(i+1\) 加 \(1\) 即可(\(fa_i\) 为 \(i\) 的父节点)。

选择偷懒

求 \(i\) 到 \(i+1\) 的 LCA。

然后进行树上差分。

假设 \(i\) 与 \(i+1\) 的 LCA 为 \(k\),\(fk\) 为 \(k\) 的父节点。

那么将 \(f_i=f_i+1,f_{i+1}=f_{i+1}+1,f_k=f_k-1,f_{fk}=f_{fk}-1\)

递归向下,回溯求解。

由于 \(i\) 节点返回不需要加值,所以把原式中 \(i\) 改为 \(fa_i\) 即可。

由于得涉及树剖,倍增求 LCA 改成树剖即可。

这里没有用树剖,懒得打。

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,t[300005],dep[300005],f[300005][25],dp[300005];
vector<int>a[300005];
void dfs(int x,int fa)
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
	int len=a[x].size();
	for(int i=0;i<len;i++)if(a[x][i]!=fa)dfs(a[x][i],x);
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int i=20;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
int Get(int x)
{
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==f[x][0])continue;
		Get(a[x][i]);
		dp[x]+=dp[a[x][i]];
	}
	return dp[x];
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&t[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a[x].push_back(y);
		a[y].push_back(x);
	}
	dfs(1,0);
	for(int i=2;i<=n;i++)
	{
		int k=lca(t[i],t[i-1]);
		dp[t[i]]++,dp[t[i-1]]++,dp[k]--,dp[f[k][0]]--;
		dp[t[i]]--,dp[f[t[i]][0]]++;
	}
	Get(1);
	for(int i=1;i<=n;i++)printf("%d\n",dp[i]);
	return 0;
}

7、P4069 [SDOI2016]游戏(合李超线段树)(暂不会李超线段树)

8、P4211 [LNOI2014]LCA(树剖求区间 LCA,较难想到)

可以想到,求 \(dep[LCA(x,y)]\) 实际上是 \(LCA(x,y)\) 到根的路径加一。

貌似感觉什么都没说。

但是如果换一种方式看,就有新的发现,把 \(dep[LCA(x,y)]\) 看成 \(x\) 走到根与 \(y\) 走到根的公共路径和加一。

那这样就会很不一样,我们只要标记出 \(x\) 所走的路径,再走 \(y\) 统计和即可,由于最后要加一,所以可以把 \(x\) 走过的边改为标记走过的点,这样就可以对 \(1\) 到 \(x\) 的路径 \(+1\),再查询 \(1\) 到 \(y\) 的路径和就行了。

每次询问树剖暴力更新 \(l\) 至 \(r\) 到 \(1\) 的值,最后求 \(z\) 到 \(1\) 的路径和。

这个很明显会 T。

考虑优化。

上述复杂度为 \(O(n\times\log_2^2(n)\times q)\)

\(n\times\log_2^2(n)\) 为树剖复杂度,很难避免,选择优化掉 \(q\)。

在每一次做区间题时,想想差分,本题利用差分求区间查询,对于每一次查询,分开为 \((l-1,z)\) 与 \((r,z)\),然后按第一位为关键字排序,每次先进行从上一次的点更新到这次的点,再计算答案即可。

复杂度:\(O(n\times\log_2^2(n))\)。

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+5;
const int mod=201314;
int n,m,dep[N],fa[N],siz[N],son[N],cnt,id[N],topp[N],f[4*N],laz[4*N],nl,nr,k,cnp,ans[N][2];
vector<int>a[N];
struct node
{
	int name,data,num;
	bool flag;
}t[2*N];
int cmp(node fi,node se)
{
	return fi.data<se.data;
}
int dfs1(int x,int fath)
{	
	dep[x]=dep[fath]+1;
	fa[x]=fath;
	siz[x]=1;
	int len=a[x].size(),maxn=0;
	for(int i=0;i<len;i++)
	{
		int num=dfs1(a[x][i],x);
		if(num>maxn)maxn=num,son[x]=a[x][i];
		siz[x]+=num;
	}
	return siz[x];
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	id[x]=++cnt;
	if(son[x])dfs2(son[x],tp);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==son[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=f[ls(x)]+f[rs(x)];
	f[x]%=mod;
}
inline void fix(int x,int l,int r,int kt)
{
	f[x]+=(r-l+1)*kt;
	f[x]%=mod;
	laz[x]+=kt;
	laz[x]%=mod;
}
inline void pushdown(int x,int l,int r)
{
	int mid=(l+r)>>1;
	fix(ls(x),l,mid,laz[x]);
	fix(rs(x),mid+1,r,laz[x]);
	laz[x]=0;
}
void update(int x,int l,int r)
{
	if(l>=nl&&r<=nr)
	{
		f[x]+=(r-l+1)*k;
		f[x]%=mod;
		laz[x]+=k;
		laz[x]%=mod;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	if(mid<nr)update(rs(x),mid+1,r);
	pushup(x);
}
int search(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return f[x];
	pushdown(x,l,r);
	int mid=(l+r)>>1,sum=0;
	if(mid>=nl)sum+=search(ls(x),l,mid);
	if(mid<nr)sum+=search(rs(x),mid+1,r);
	return sum;
}
void update_road(int x,int y)
{
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		update(1,1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y]; 
	update(1,1,n);
}
int search_road(int x,int y)
{
	int sum=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		sum+=search(1,1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	sum+=search(1,1,n);
	return sum;
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=2;i<=n;i++)
	{
		int u;
		scanf("%lld",&u);
		a[u+1].push_back(i);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		x++,y++,z++;
		t[++cnp].name=i;
		t[cnp].data=x-1;
		t[cnp].num=z;
		t[cnp].flag=0;
		t[++cnp].name=i;
		t[cnp].data=y;
		t[cnp].num=z;
		t[cnp].flag=1;
	}
	sort(t+1,t+1+cnp,cmp);
	int bef=1;
	k=1;
	for(int i=1;i<=cnp;i++)
	{
		for(int j=bef;j<=t[i].data;j++)update_road(1,j);
		bef=t[i].data+1;
		ans[t[i].name][t[i].flag]=search_road(1,t[i].num)%mod;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",(ans[i][1]-ans[i][0]%mod+mod)%mod);
	return 0;
}

9、P4592 [TJOI2018]异或(树剖+可持久化01trie)(未做)

10、P5305 [GXOI/GZOI2019]旧词(会了 P4211 不算特别难)

与第八题一模一样。

由于有指数,所以先预处理所有深度的贡献,即 \(dep^k-(dep-1)^k\),然后预处理出来线段树上所有点的深度贡献值,再求前缀和,然后更新线段树时求前缀和即可。

好水的黑题

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int N=5e4+5;
const int mod=998244353;
int n,m,dep[N],fa[N],siz[N],son[N],cnt,id[N],topp[N],f[4*N],laz[4*N],nl,nr,k,cnp,ans[N],dc[N],res[N];
vector<int>a[N];
int quick_pow(int x,int y)
{
	if(y==0)return 0;
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=mod;
		num*=num;
		num%=mod;
		y>>=1;
	}
	return sum;
}
struct node
{
	int name,data,num;
}t[2*N];
int cmp(node fi,node se)
{
	return fi.data<se.data;
}
int dfs1(int x,int fath)
{	
	dep[x]=dep[fath]+1;
	fa[x]=fath;
	siz[x]=1;
	int len=a[x].size(),maxn=0;
	for(int i=0;i<len;i++)
	{
		int num=dfs1(a[x][i],x);
		if(num>maxn)maxn=num,son[x]=a[x][i];
		siz[x]+=num;
	}
	return siz[x];
}
void dfs2(int x,int tp)
{
	topp[x]=tp;
	id[x]=++cnt;
	if(son[x])dfs2(son[x],tp);
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==son[x])continue;
		dfs2(a[x][i],a[x][i]);
	}
}
inline int ls(int x)
{
	return x<<1;
}
inline int rs(int x)
{
	return x<<1|1;
}
inline void pushup(int x)
{
	f[x]=f[ls(x)]+f[rs(x)];
	f[x]%=mod;
}
inline void fix(int x,int l,int r,int kt)
{
	f[x]+=(res[r]-res[l-1])*kt;
	f[x]%=mod;
	laz[x]+=kt;
	laz[x]%=mod;
}
inline void pushdown(int x,int l,int r)
{
	int mid=(l+r)>>1;
	fix(ls(x),l,mid,laz[x]);
	fix(rs(x),mid+1,r,laz[x]);
	laz[x]=0;
}
void update(int x,int l,int r)
{
	if(l>=nl&&r<=nr)
	{	
		f[x]+=res[r]-res[l-1];
		f[x]%=mod;
		laz[x]++;
		laz[x]%=mod;
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(mid>=nl)update(ls(x),l,mid);
	if(mid<nr)update(rs(x),mid+1,r);
	pushup(x);
}
int search(int x,int l,int r)
{
	if(l>=nl&&r<=nr)return f[x];
	pushdown(x,l,r);
	int mid=(l+r)>>1,sum=0;
	if(mid>=nl)sum+=search(ls(x),l,mid);
	if(mid<nr)sum+=search(rs(x),mid+1,r);
	return sum;
}
void update_road(int x,int y)
{
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		update(1,1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y]; 
	update(1,1,n);
}
int search_road(int x,int y)
{
	int sum=0;
	while(topp[x]!=topp[y])
	{
		if(dep[topp[x]]<dep[topp[y]])swap(x,y);
		nl=id[topp[x]],nr=id[x];
		sum+=search(1,1,n);
		x=fa[topp[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	nl=id[x],nr=id[y];
	sum+=search(1,1,n);
	return sum;
}
signed main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	for(int i=1;i<=n;i++)dc[i]=((quick_pow(i,k)-quick_pow(i-1,k))%mod+mod)%mod;
	for(int i=2;i<=n;i++)
	{
		int u;
		scanf("%lld",&u);
		a[u].push_back(i);
	}
	dfs1(1,0);
	dfs2(1,1);
	for(int i=1;i<=n;i++)res[id[i]]=dc[dep[i]];
	for(int i=1;i<=n;i++)res[i]=(res[i]+res[i-1])%mod;
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%lld%lld",&x,&y);
		t[++cnp].name=i;
		t[cnp].data=x;
		t[cnp].num=y;
	}
	sort(t+1,t+1+cnp,cmp);
	int bef=1;
	for(int i=1;i<=cnp;i++)
	{
		for(int j=bef;j<=t[i].data;j++)update_road(1,j);
		bef=t[i].data+1;
		ans[t[i].name]=search_road(1,t[i].num)%mod;
	}
	for(int i=1;i<=m;i++)printf("%lld\n",(ans[i]%mod+mod)%mod);
	return 0;
}

标签:练习题,nl,树剖,int,题解,mid,dep,return,id
From: https://www.cnblogs.com/gmtfff/p/17151276.html

相关文章

  • P3571 [POI2014]SUP-Supercomputer 题解
    首先有一个结论,树中存在一个深度\(dep\),使得深度小于等于\(dep\)的点只需\(dep\)次覆盖完,而大于\(dep\)的除最后一次外其他每次都可以填充\(k\)次。证明:在\(dep......
  • P4768 [NOI2018] 归程 题解
    这是一个悲伤的题目,自这道题后,\(\text{Noi}\)再无\(\text{SPFA}\)。首先讲一下重构树是啥。重构树是基于\(\text{kurskal生成树}\)算法的一棵树,对于每一次合并一条......
  • P3247 [HNOI2016]最小公倍数 题解
    题意简述:给定一个无向图,边权带两个值\((a,b)\),给定\(q\)次询问,每次询问给定两个点,求两个点直接是否有\(\max(a)=x\)且\(\max(b)=y\)的简单或非简单路径。解:如果......
  • P2489 [SDOI2011]迷宫探险 题解
    题意简述:一个\(n\timesm\)的带墙体单入口多出口迷宫中有\(k\)个陷阱,陷阱分为有害或无害,有害会使人掉血,给出所有垃圾的有害与无害的所有排列组成的概率,给定人的血量,求......
  • P4416 [COCI2017-2018#1] Plahte 题解
    题意简述:给定\(n\)个互不相交,可以重叠的矩阵,对某些点染色,这个点上的所有矩阵会被染上这个颜色,求最后每个矩阵会有多少种颜色。解:我们可以先把矩阵拆成上下两条水平线......
  • CF1034A Enlarge GCD 题解
    题意简述:删去最少的数使所有的数的\(\text{gcd}\)增加。解:先对每个数除以所有数的\(\text{gcd}\),然后剩下的需要找到所有数的质因子,找到一个最多的序列中数拥有的质......
  • CF436E Cardboard Box 题解
    一道经典的反悔贪心题。考虑每次选择使总星数加一,那么总共有四种情况。一、对于一关没有星,选一颗星,代价为\(a_i\)。二、对于一关有一颗星,再选一颗星,代价为\(b_i-a_i\)......
  • CF204E Little Elephant and Strings 题解
    由于是多个串,还与每个子串的信息有关,很容易想到用SA或广义SAM。这里选择用SA。首先先把字符串转化为数组,连接起来,中间用一些不会出现的数。处理出后缀数组与\(height......
  • P5842 [SCOI2012]Blinker 的仰慕者 题解
    题意简述:求\([l,r]\)内各个数位积为\(k\)的数的和。解:在看题解前,请先确认自己是否精通了数位dp模板题,否则会很难理解。对于朴素的数位dp,我们可以记录\(f_{i,j......
  • P5416 [CTSC2016]时空旅行 题解
    首先题中的\(y,z\)好像没啥用……首先对于每一次询问,要求\(\min((x_0-x_i)^2+c_i)\)设\((x_0-x_i)^2+c_i=ans\)。那么\(x_0^2+x_i^2-2x_0x_i+c_i=ans\)所以\(x_0......