首页 > 其他分享 >两棵树问题的一种点分治做法

两棵树问题的一种点分治做法

时间:2024-10-24 12:32:36浏览次数:5  
标签:sta fa int siz 分治 stop dep 做法 两棵树

简述题面:

你有两棵树,\(T_1\) ,\(T_2\) ,然后你需要对于每个点求出 \(\min_{j\not=i}(dist(T_1,i,j)+dist(T_2,i,j))\)
要求时间复杂度 \(O(n\log^2 n)\) 或更优

做法:

考虑点分治,假如在 \(T_1\) 固定 \(i,j\) 一定要经过某个 \(x\) ,然后把 \(x\) 作为分治点,那么实际上 \(val[i,j]=dist(T_2,i,j)+dep(T_1,i)+dep(T_1,j)\)
其中 \(dep\) 表示距离分治点的距离,然后发现这个就是一个只关于 \(T_2\) 上距离的加和的东西,类似于换根 dp 的问题,具体的就是把 \(T_1\) 的分治点接管的所有 \(T_1\) 上的节点在 \(T_2\) 上的虚树建出来,然后再虚树上跑 换根 dp ,然后每层分治点的并是 \(O(n)\) 的,所以时间复杂度是 \(O(n\log n) \cdot O(\log n)\) (建虚树)的

赛时代码

#pragma GCC optimize("Ofast")
#pragma GCC optimize(2)
#pragma GCC optimize(3)

#include <bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pb push_back
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch))f^=ch=='-',ch=getchar();
	while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
	return f?x:-x;
}
const int N=1e5+5,inf=1e18;
int ans[N],n;
vector<pair<int,int>> T[N];
int top[N],siz[N],son[N],up[N],dfn[N],dep[N],dfntot;
void dfs1(int u,int fa){
	dfn[u]=++dfntot;
	up[u]=fa;
	siz[u]=1;
	for(pair<int,int> e:T[u]){
		int v=e.fi,w=e.se;
		if(v==fa)continue;
		dep[v]=dep[u]+w;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[son[u]]<siz[v])
			son[u]=v;
	}
}
void dfs2(int u,int tp){
	top[u]=tp;
	if(son[u])dfs2(son[u],tp);
	for(pair<int,int> e:T[u]){
		int v=e.fi,w=e.se;
		if(v==up[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		x=up[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	return x;
}
/*虚树part*/
vector<pair<int,int>> vt[N];
int sta[N],stop,vis[N];
void insert(int x){
	if(stop<=1){
		sta[++stop]=x;
		return;
	}
	int t=lca(x,sta[stop]);
	if(t==sta[stop]){
		sta[++stop]=x;
		return;
	}
	while(stop>1&&dfn[sta[stop-1]]>=dfn[t]){
		int u=sta[stop-1];
		int v=sta[stop--];
		vt[u].pb({v,dep[v]-dep[u]});
	}
	if(sta[stop]!=t){
		vt[t].pb({sta[stop],dep[sta[stop]]-dep[t]});
		sta[stop]=t;
	}
	sta[++stop]=x;
}
vector<int> sss;
void build(vector<int> st){
	sss=st;
	int len=sss.size();
	sort(sss.begin(),sss.end(),[](int x,int y){
		return dfn[x]<dfn[y];
	});
	stop=0;
	for(int v:sss)vis[v]=1;
	if(!vis[1])insert(1);
	for(int v:sss)insert(v);
	while(stop>1){
		int u=sta[stop-1];
		int v=sta[stop--];
		vt[u].pb({v,dep[v]-dep[u]});
	}
}
int f[N],g[N],d[N];
void pmin(int &x,int &y,int z){
	if(z<=x){
		y=x;
		x=z;
	}
	else y=min(y,z);
}
void dp1(int u,int fa){
	f[u]=g[u]=inf;
	for(pair<int,int> e:vt[u]){
		int v=e.fi,w=e.se;
		if(v==fa)continue;
		dp1(v,u);
		pmin(f[u],g[u],f[v]+w);
	}
	if(vis[u])pmin(f[u],g[u],d[u]);
}
void dp2(int u,int fa){
	for(pair<int,int> e:vt[u]){
		int v=e.fi,w=e.se;
		if(v==fa)continue;
		if(f[v]+w==f[u]){
			pmin(f[v],g[v],g[u]+w);
		}
		else{
			pmin(f[v],g[v],f[u]+w);
		}
		dp2(v,u);
	}
}
void dp3(int u,int fa){
	if(vis[u]){
		int tmpu=0;
		if(d[u]==f[u])tmpu=g[u];
		else tmpu=f[u];
		ans[u]=min(ans[u],d[u]+tmpu);
	}
	for(pair<int,int> e:vt[u]){
		int v=e.fi,w=e.se;
		if(v==fa)continue;
		dp3(v,u);
	}
	vis[u]=0;
	vt[u].clear();
}
/*虚树part-end*/
struct tree2{
	vector<pair<int,int>> T[N];
	int vis[N],siz[N],f[N],root,sum,dep[N];
	vector<int> nodes;
	void getsize(int u,int fa){
		siz[u]=1;
		for(pair<int,int> e:T[u]){
			int v=e.fi;
			if(v==fa||vis[v])continue;
			getsize(v,u);
			siz[u]+=siz[v];
		}
	}
	void getroot(int u,int fa){
		f[u]=0;
		for(pair<int,int> e:T[u]){
			int v=e.fi;
			if(v==fa||vis[v])continue;
			getroot(v,u);
			f[u]=max(f[u],siz[v]);
		}
		f[u]=max(f[u],sum-siz[u]);
		if(f[u]<f[root])root=u;
	}
	void dfs1(int u,int fa){
		for(pair<int,int> e:T[u]){
			int v=e.fi,w=e.se;
			if(vis[v]||v==fa)continue;
			dep[v]=dep[u]+w;
			dfs1(v,u);
		}
		nodes.pb(u);
	}
	void solve(int u){
		nodes.clear();
		dep[u]=0;
		dfs1(u,0);
		for(int x:nodes){
			d[x]=dep[x];
		}
		build(nodes);
		dp1(1,0);
		dp2(1,0);
		dp3(1,0);
		vis[u]=1;
		for(pair<int,int> e:T[u]){
			int v=e.fi,w=e.se;
			if(vis[v])continue;
			getsize(v,u);
			sum=siz[v];
			root=0;
			f[root]=inf;
			getroot(v,u);
			solve(root);
		}
	}
	void work(){
		sum=n;
		root=0;
		f[0]=inf;
		getsize(1,0);
		getroot(1,0);
		solve(root);
	}
}T2;
signed main(){
//	freopen("sample2.in","r",stdin);
//	freopen("tester.out","w",stdout);
	n=read();
	for(int i=1;i<=n;++i)ans[i]=inf;
	for(int i=1;i<n;++i){
		int x=read(),y=read(),z=read();
		T[x].pb({y,z});
		T[y].pb({x,z});
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<n;++i){
		int x=read(),y=read(),z=read();
		T2.T[x].pb({y,z});
		T2.T[y].pb({x,z});
	}
	T2.work();
	for(int i=1;i<=n;++i){
		printf("%lld\n",ans[i]);
	}
	return 0;
}

一些补充

实际上我虽然固定了 \((i,j)\) 经过 \(x\) 但是建出虚树后不一定能够保证 \((i,j)\) 一定在 \(x\) 的不同子树中,但是没关系,因为如果 \((i,j)\) 的 \(lca\) 是 \(x\) 的后代,那么我会在 \(lca\) 处再一次统计 (i,j) ,而我在 \(x\) 处统计出的 \((i,j)\) 的答案一定不优于在 \(lca\) 处统计的答案

标签:sta,fa,int,siz,分治,stop,dep,做法,两棵树
From: https://www.cnblogs.com/chenhx-xcpc/p/18499352

相关文章

  • 线段树分治学习笔记
    前置知识:线段树(只需要了解其结构)支持撤销的数据结构,如可撤销并查集,可持久化数据结构等。线段树分治是什么一种离线算法,用于处理假如某些信息会在某个时间段出现,求某个时刻的信息并。常见的离线算法还有CDQ分治,考虑一下这两个算法的区别。CDQ分治:对于每个操作,考虑其对后面询......
  • 分治法求最大连续子序列的积
    1.源代码#include<iostream>#include<vector>#include<string>#include<sstream>usingnamespacestd;intmax3(intnum1,intnum2,intnum3){   if(num1>num2){      num2=num1;   }   returnnum2>num3?num2:n......
  • 分治---归并排序
    参考资料水平有限,欢迎交流!【如何优雅地排序——3分钟看懂【归并排序】的基本思想】练习题P1177【模板】排序-洛谷|计算机科学教育新生态(luogu.com.cn)LCR170.交易逆序对的总数-力扣(LeetCode)关键步骤与应用步骤在归并过程中,主要包含两个关键步骤:分割(Divide):将......
  • 【数据结构】分治算法经典: 快速排序详解
    快速排序(Quicksort)是一种高效的排序算法,最早由TonyHoare在1960年提出。它采用了分治(DivideandConquer)策略,平均时间复杂度为O(nlog......
  • 点分治相关
    点分治点分治适合处理大规模的树上路径信息问题。实现P3806【模板】点分治1给定一棵有\(n\)个点的树,询问树上距离为\(k\)的点对是否存在。\(n\leq10^4\)考虑简单深搜,对于任意子树,把路径分为经过根节点的路径和不经过根节点的路径。对于不经过根节点的路径,我们递归......
  • 数据结构与算法篇(回溯&递归&分治 - 刷题篇)(目前一天图片上传太多加载不出来)(后续更新)
    目录1.没有重复项数字的全排列(中等)1.1.题目描述1.2解题思路1.3代码实现方法一:递归方法二:非递归版2.有重复项数字的全排列(中等)2.1.题目描述2.2.解题思路2.3.代码实现递归+回溯(推荐使用)3.岛屿数量(中等)3.1.题目描述3.2.解题思路3.3代码实现方法一:dfs......
  • 【分治】线段树 SegmentTree
    算法描述线段树是一种能够处理区间修改和区间查询的数据结构。顾名思义,线段树就是一种存储着线段数据的树形结构。它的每个节点都表示一个线段区间,每个节点的孩子节点存储的就是该区间的左半段和右半段。每个线段区间都存储着一个值,一般是区间和,也有可能是区间最大/最小值。......
  • 线段树分治略解&杂题解析
    可能做到好题之后会再更新吧。总叙线段树与离线询问结合技巧又被称为线段树分治。使用线段树分治维护的信息通常会在某一个时间段内出现,要求在离线的前提下回答某一个时刻的信息并,则可以考虑使用线段树分治的技巧。以下是线段树分治的基本模板:change将信息按时间段覆盖在线......
  • 树分治
    点分树关于模拟赛\(T2\)考点分树这件事。点分治点分树被称为动态点分治,所以下面先介绍点分治。P3806【模板】点分治1点分治板子。考虑一个树上的路径,如果已知所有\(dis,\)可以按是否经过根划分为:经过根:\(dis[u]+dis[v],\)这个方便用桶处理。没有经过根:经过了根的子......
  • 点分治
    感觉非常有深度,感觉过几天就又要忘了,所以我写个题解。P3806【模板】点分治1给定一棵有\(n\)个点的树,询问树上距离为\(k\)的点对是否存在。题意非常简单题意越短越毒瘤。大佬原文我们先想想点对有几种情况:第一种是经过根节点的路径;第二种是不经过根节点的路径;想第一......