The 13th Chinese Northeast Collegiate Programming Contest D. Master of Data Structure(虚树)

这篇博客介绍了如何利用虚树和树状DP来优化树上的操作,包括路径上点权的加法、异或、减法、询问和最大值最小值差等操作。通过建立虚树,可以将复杂的问题简化,提高算法效率。文章提供了两种不同的实现方式,一种是树形DP式的建树,另一种是常规建树并维护边权信息。每种方法都在不超过一定数量的节点上进行维护,确保了算法的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

6s时限,T(T<=5)组样例,每次给出n(n<=5e5)个点的树,初始时,每个点的点权w都是0,

m(m<=2e3)次操作,操作分7种,

1 u v k,u到v的路径所有点点权都加k(k<=1e5)

2 u v k,u到v的路径所有点点权都异或k(k<=1e8)

3 u v k,u到v的路径所有点点权都减k,如果点权小于k,忽略该操作

4 u v,询问u到v的路径点权和

5 u v,询问u到v的路径点权异或和

6 u v,询问u到v的路径点权中的最大值减最小值

7 u v k,询问u到v的路径点权中与k最接近的数与k的绝对值之差(k<=1e8)

思路来源

https://www.cnblogs.com/LiuRunky/p/Virtual_Tree.html 常规建虚树,维护边权

https://blog.youkuaiyun.com/cat_pikapikapi/article/details/90673849 树形dp式建树

题解

建虚树,把关键点和lca建到虚树里,

lca和关键点之间有一些链,链上的点也需要缩成一个点,建在虚树里,其实就是树上离散化

其中蓝点是询问的点,绿点是虚树保留的lca,

橙点是原来这条链上需要参与计算的点缩的点,

 

根据实现方式不同,

第一种代码采用类似树形dp的方式建虚树,

第二种代码常规建虚树,把一条链维护的信息放在了关键点和lca之间的边上来维护,

更简单无脑一点,即把橙点当成蓝绿点之间的边

建完虚树之后,就暴力维护7种操作即可,

M个询问,最多出现2M个点,包括lca后4M个点,

则第一种建树方法,虚树点不会超过16000,第二种不会超过8000

代码1(树型dp建树)

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const ll INF=1e17;
const int N=5e5+10,M=2e3+10;
ll w[N]; 
int t,n,m,x,y,dep[N],par[N];
int chain[N],now[N];//chain:缩出来的链长 now:当前点子树里离当前点最近的关键点 
bool sub[N],is[N];//sub:子树里是否有关键点 is:是否是关键点 
vector<int>e[N];
int op[M],u[M],v[M],k[M];
void dfs(int u,int fa){
	int sum=0,pos;
	chain[u]=1;
	now[u]=u; 
	for(int v:e[u]){
		if(v==fa)continue;
		dep[v]=dep[u]+1;
		dfs(v,u);
		if(!sub[v])continue;//防止改变pos 
		sub[u]|=sub[v];
		sum+=sub[v];
		pos=now[v];
	}
	if(!sub[u])return;
	if(!is[u] && sum==1){//只有一个子树有关键点 
		if(is[pos]){//是关键点 
			par[pos]=u;
		}
		else{//是链缩出来的点 
			now[u]=pos;
			chain[pos]++;
		}
		return; 
	}
	is[u]=1;//两个子树的lca 是关键点
	for(int v:e[u]){
		if(v==fa || !sub[v])continue;
		par[now[v]]=u; 
	} 
}
ll cal(int op,int x,int y,int v){
	ll mx=-INF,mn=INF,res=0;
	while(1){
		if(dep[x]<dep[y])swap(x,y);
		switch(op){
			case 1:w[x]+=v;break;
			case 2:w[x]^=v;break;
			case 3:if(w[x]>=v)w[x]-=v;break;
			case 4:res+=w[x]*chain[x];break;
			case 5:if(chain[x]&1)res^=w[x];break;
			case 6:mx=max(mx,w[x]);mn=min(mn,w[x]);res=mx-mn;break;
			case 7:mn=min(mn,abs(w[x]-v));res=mn;break;
		} 
		if(x==y)break;
		x=par[x];
	}
	return res;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i){
			e[i].clear();
			sub[i]=is[i]=w[i]=dep[i]=0;
		}
		for(int i=1;i<n;++i){
			scanf("%d%d",&x,&y);
			e[x].pb(y);e[y].pb(x);
		}
		for(int i=1;i<=m;++i){
			scanf("%d%d%d",&op[i],&u[i],&v[i]);
			if(op[i]<4 || op[i]>6)scanf("%d",&k[i]);
			is[u[i]]=is[v[i]]=1;
			sub[u[i]]=sub[v[i]]=1;
		}
		dfs(1,-1);
		for(int i=1;i<=m;++i){
			ll ans=cal(op[i],u[i],v[i],k[i]);
			if(op[i]>3)printf("%lld\n",ans);
		}
	}
	return 0;
} 

代码2(常规建树)

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const ll INF=1e17;
const int N=500010,M=2e3+10,LG=20;
int t,n,m,x,y;
int op[M],u[M],v[M],k[M];
vector<int>e[N];
int head[N],cnt,nex[N],to[N];//虚树 
ll w[N+4*M];//2M个点的询问 4M个点的虚树 
int stk[N],top;//虚树栈 
int dep[N],f[N][LG+1];//lca
bool key[N];//关键点 
void add(int u,int v){//par[u]=v
	nex[++cnt]=head[u];
	to[cnt]=v;
	head[u]=cnt;
}
int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=LG;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]){
			x=f[x][i];
		}
	}
	if(x==y)return x;
	for(int i=LG;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			x=f[x][i],y=f[y][i];
		}	
	}
	return f[x][0];
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	f[u][0]=fa;
	for(int i=1;i<=LG;i++){
		f[u][i]=f[f[u][i-1]][i-1];
	}
	if(key[u]){
		int p=lca(u,stk[top]);
		if(p!=stk[top]){
			while(dep[stk[top-1]]>dep[p]){
				add(stk[top],stk[top-1]);
				top--;
			}
			add(stk[top],p);
			top--;
			if(stk[top]!=p){
				stk[++top]=p;
			}
		}
		stk[++top]=u;
	}
	for(int v:e[u]){
		if(v==fa)continue;
		dfs(v,u);
	}
}
void build(){//以0为虚根 
	top=1;
	dfs(1,0);
	for(int i=top;i>=1;i--){
		add(stk[i],stk[i-1]);	
	} 
} 
ll cal(int op,int x,int y,int v){
	ll mx=-INF,mn=INF,res=0;
	int fa;
	while(1){
		if(dep[x]<dep[y])swap(x,y);
		for(int i=head[x];i;i=nex[i]){
			fa=to[i];
			switch(op){//点 
				case 1:w[x]+=v;break;
				case 2:w[x]^=v;break;
				case 3:if(w[x]>=v)w[x]-=v;break;
				case 4:res+=w[x];break;
				case 5:res^=w[x];break;
				case 6:mx=max(mx,w[x]);mn=min(mn,w[x]);res=mx-mn;break;
				case 7:mn=min(mn,abs(w[x]-v));res=mn;break;
			}
			if(x!=y){//边 
				int num=dep[x]-dep[fa]-1;
				if(num){
					switch(op){
						case 1:w[n+i]+=v;break;
						case 2:w[n+i]^=v;break;
						case 3:if(w[n+i]>=v)w[n+i]-=v;break;
						case 4:res+=w[n+i]*num;break;
						case 5:if(num&1)res^=w[n+i];break;
						case 6:mx=max(mx,w[n+i]);mn=min(mn,w[n+i]);res=mx-mn;break;
						case 7:mn=min(mn,abs(w[n+i]-v));res=mn;break;
					}
				}
			}
		}
		if(x==y)break;
		x=fa;
	}
	return res;
}
void init(){
	cnt=0;
	memset(w,0,sizeof w);  
	for(int i=1;i<=n;++i){
		e[i].clear();
		key[i]=head[i]=0;
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1;i<n;++i){
			scanf("%d%d",&x,&y);
			e[x].pb(y);e[y].pb(x);
		}
		for(int i=1;i<=m;++i){
			scanf("%d%d%d",&op[i],&u[i],&v[i]);
			if(op[i]<4 || op[i]>6)scanf("%d",&k[i]);
			key[u[i]]=key[v[i]]=1;
		}
		build();
		for(int i=1;i<=m;++i){
			ll ans=cal(op[i],u[i],v[i],k[i]);
			if(op[i]>3)printf("%lld\n",ans);
		}
	} 
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值