洛谷题面传送门

神仙题。

首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑。考虑每次找出 \(T_2\) 的重心进行点分治。然后考虑跨过分治中心的点对之间的连边情况。由于连边边权与两棵树都有关,直接处理这个“跨过重心”不太方便。不过注意到一个性质,那就是对于同一棵子树中的两个点 \(x,y\),如果我们直接将它们的边权设为 \(dep_x+dep_y+\text{dist}(x,y)\),其中 \(dep_x\) 为 \(x\) 到分治重心的距离,\(\text{dist}(x,y)\) 为 \(x,y\) 在第一棵树上经过所有边的权值之和,那么由于我们要跑最小生成树并且这个值大于实际边权,且实际边权显然会在更深层次的分治过程中扫描到,因此我们直接令边权为 \(dep_x+dep_y+\text{dist}(x,y)\) 不会出问题。

考虑令边权为 \(dep_x+dep_y+\text{dist}(x,y)\) 之后怎样解决问题。考虑建出连通块内的点在 \(T_1\) 中的虚树,然后对于每个在分治重心所在连通块中的点 \(x\),我们在虚树中另建一个虚点 \(x'\),并连一条 \(x'\) 到 \(x\),权值为 \(dep_x\) 的边,那么分治重心所在连通块中两点 \(x,y\) 之间的距离就是 \(x',y'\) 在虚树上的距离。我们称这样的 \(x'\) 为”关键点“,那么我们考虑换根 \(dp\) 求出距离虚树上每个点最近的关键点 \(nr_x\),那么我们枚举每一条边 \((x,y)\),我们考虑找出割掉这条边后,与 \(x\) 在同一个连通块,且与 \(x\) 最近的关键点 \(u\),以及与 \(y\) 在同一个连通块,且与 \(y\) 最近的关键点 \(v\)——这个可以通过之前换根 dp 时预处理的数组求出,那么我们需要保留原图中 \(u,v\) 中的边,对于没有被保留的边直接扔掉即可。因为考虑一条可能在最小生成树中的边 \((u,v)\),那么其必然存在一个时刻作为跨越重心的路径存在——具体来说就是分治到它们点分树上的 LCA 作为重心时同时作为关键点出现在虚树中,那么有一个性质就是此时,\(T_1\) 中 \(u\to v\) 路径上的点的路径的 \(nr\) 必然是前一段是 \(u\),后一段是 \(v\),否则我们可以找到一个 \(nr_w\ne u,nr_w\ne v\) 的点 \(w\),然后将 \(u\to v\) 的边改成 \((u,nr_w),(nr_w,v)\),答案一定不会变得更劣。

这样一来我们就只用保留 \(n\log n\) 条有用的边,排序做 Kruskal 即可。时间复杂度 \(n\log^2n\)。

const int MAXN=1e5;
const int MAXE=4e6;
const int LOG_N=19;
const int INF=0x3f3f3f3f;
int n;
struct tree{
int hd[MAXN+5],to[MAXN*2+5],val[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v,int w){to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;}
int dfn[MAXN+5],tim=0,dep[MAXN+5],dis[MAXN+5];
int dfn_eu[MAXN+5],tim_eu=0;pii st[MAXN*2+5][LOG_N+2];
void dfs(int x,int f){
st[dfn_eu[x]=++tim_eu][0]=mp(dep[x],x);
dfn[x]=++tim;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=val[e];if(y==f) continue;
dep[y]=dep[x]+1;dis[y]=dis[x]+z;dfs(y,x);
st[dfn_eu[x]=++tim_eu][0]=mp(dep[x],x);
}
}
pii query_st(int l,int r){
int k=31-__builtin_clz(r-l+1);
return min(st[l][k],st[r-(1<<k)+1][k]);
}
int getlca(int x,int y){
x=dfn_eu[x];y=dfn_eu[y];if(x>y) swap(x,y);
return query_st(x,y).se;
}
int getdis(int x,int y){return dis[x]+dis[y]-(dis[getlca(x,y)]<<1);}
void prework(){
dfs(1,0);
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n*2;j++)
st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
// printf("LCA:\n");
// for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
// printf("%d%c",getlca(i,j)," \n"[j==n]);
}
} T1,T2;
#define lca1(x,y) T1.getlca(x,y)
#define lca2(x,y) T2.getlca(x,y)
int siz[MAXN+5],mx[MAXN+5],cent=0;bool vis[MAXN+5];
void findcent(int x,int f,int totsiz){
siz[x]=1;mx[x]=0;//printf("%d %d %d\n",x,f,totsiz);
for(int e=T2.hd[x];e;e=T2.nxt[e]){
int y=T2.to[e];if(y==f||vis[y]) continue;
findcent(y,x,totsiz);chkmax(mx[x],siz[y]);
siz[x]+=siz[y];
} chkmax(mx[x],totsiz-siz[x]);
if(mx[x]<mx[cent]) cent=x;
}
vector<int> pt;ll dis[MAXN+5];
void findpts(int x,int f){
pt.pb(x);
for(int e=T2.hd[x];e;e=T2.nxt[e]){
int y=T2.to[e],z=T2.val[e];
if(y==f||vis[y]) continue;
dis[y]=dis[x]+z;findpts(y,x);
}
}
vector<pii> g[MAXN*2+5];
int stk[MAXN+5],tp=0,rt;
void adde(int u,int v){
int len=T1.dis[v]-T1.dis[u];
// printf("%d %d %d\n",u,v,len);
g[u].pb(mp(v,len));g[v].pb(mp(u,len));
}
void insert(int x){
if(!tp) return stk[++tp]=x,void();
int lc=lca1(x,stk[tp]);
while(tp>1&&T1.dep[stk[tp-1]]>T1.dep[lc]) adde(stk[tp-1],stk[tp]),tp--;
if(tp&&T1.dep[stk[tp]]>T1.dep[lc]) adde(lc,stk[tp--]);
if(!tp||stk[tp]!=lc) stk[++tp]=lc;
stk[++tp]=x;
}
void fin(){
while(tp>=2) adde(stk[tp-1],stk[tp]),--tp;
stk[tp--]=0;
}
void build_virt(){
// printf("building virtual tree\n");
sort(pt.begin(),pt.end(),[&](int x,int y){return T1.dfn[x]<T1.dfn[y];});
for(int i=0;i<pt.size();i++) insert(pt[i]);fin();
rt=lca1(pt[0],pt.back());
for(int i=0;i<pt.size();i++){
int x=pt[i];
g[x].pb(mp(x+n,dis[x]));
g[x+n].pb(mp(x,dis[x]));
}
}
pii dp1[MAXN*2+5],dp2[MAXN*2+5];
void dfs1(int x,int f){
if(x>n) dp1[x]=mp(0,x);
else dp1[x]=mp(INF,0);
for(pii p:g[x]){
int y=p.fi,z=p.se;if(y==f) continue;dfs1(y,x);
chkmin(dp1[x],mp(dp1[y].fi+z,dp1[y].se));
} //printf("dp %d %d %d\n",x,dp1[x].fi,dp1[x].se);
}
void dfs2(int x,int f){
multiset<pii> out;
for(pii p:g[x]){
int y=p.fi,z=p.se;
if(y==f) out.insert(dp2[x]);
else out.insert(mp(dp1[y].fi+z,dp1[y].se));
} if(x>n) out.insert(mp(0,x));
else out.insert(mp(INF,0));
for(pii p:g[x]){
int y=p.fi,z=p.se;if(y==f) continue;
out.erase(out.find(mp(dp1[y].fi+z,dp1[y].se)));
pii pp=*out.begin();dp2[y]=mp(pp.fi+z,pp.se);
out.insert(mp(dp1[y].fi+z,dp1[y].se));
dfs2(y,x);
}
}
void calc_dp(){dfs1(rt,0);dp2[rt]=mp(INF,0);dfs2(rt,0);}
struct edge{
int u,v,w;
edge(int _u=0,int _v=0,int _w=0):u(_u),v(_v),w(_w){}
bool operator <(const edge &rhs){return w<rhs.w;}
} e[MAXE+5];
int ecnt=0;
int get(int x){return min(dp1[x],dp2[x]).se-n;}
int calc_wei(int x,int y){return T1.getdis(x,y)+T2.getdis(x,y);}
void dfsadd(int x,int f){
for(pii p:g[x]){
int y=p.fi,z=p.se;if(y==f) continue;
if(dp1[y].se>n&&dp2[y].se>n){
e[++ecnt]=edge(dp1[y].se-n,dp2[y].se-n,calc_wei(dp1[y].se-n,dp2[y].se-n));
} dfsadd(y,x);
}
}
void clear(int x,int f){
for(pii p:g[x]) if(p.fi!=f) clear(p.fi,x);
g[x].clear();
}
void divcent(int x){
// printf("divcent %d\n",x);
vis[x]=1;pt.clear();dis[x]=0;findpts(x,0);
// printf("points below:\n");
// for(int p:pt) printf("%d %d\n",p,dis[p]);
build_virt();calc_dp();dfsadd(rt,0);clear(rt,0);
// for(int i=1;i<=n*2;i++) g[i].clear();
for(int e=T2.hd[x];e;e=T2.nxt[e]){
int y=T2.to[e];if(vis[y]) continue;
cent=0;findcent(y,x,siz[y]);divcent(cent);
}
}
struct ufset{
int f[MAXN+5];
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
bool merge(int x,int y){x=find(x);y=find(y);return (x^y)?(f[x]=y,1):0;}
} dsu;
int main(){
freopen("child.in","r",stdin);
freopen("child.out","w",stdout);
scanf("%d",&n);
for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),T1.adde(u,v,w),T1.adde(v,u,w);
for(int i=1,u,v,w;i<n;i++) scanf("%d%d%d",&u,&v,&w),T2.adde(u,v,w),T2.adde(v,u,w);
T1.prework();T2.prework();
mx[0]=INF;findcent(1,0,n);divcent(cent);
sort(e+1,e+ecnt+1);ll res=0;
for(int i=1;i<=ecnt;i++) if(dsu.merge(e[i].u,e[i].v)) res+=e[i].w;
printf("%lld\n",res);
return 0;
}
/*
6
1 2 2
1 3 4
1 4 3
2 5 2
2 6 3
1 4 1
4 5 2
4 6 3
5 2 4
6 3 5
*/

洛谷 P6199 - [EER1]河童重工(点分治+虚树)的更多相关文章

  1. 洛谷P4220 [WC2018]通道(边分治+虚树)

    题面 传送门 题解 代码不就百来行么也不算很长丫 虽然这题随机化贪心就可以过而且速度和正解差不多不过我们还是要好好学正解 前置芝士 边分治 米娜应该都知道点分治是个什么东西,而边分治,顾名思义就是对边 ...

  2. 洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP

    洛谷 P4093 [HEOI2016/TJOI2016]序列 CDQ分治优化DP 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某宝上买了一个有趣的玩具送给他. 玩具上有一个数列,数列中某些项的值可能会 ...

  3. 【洛谷5439】【XR-2】永恒(树链剖分,线段树)

    [洛谷5439][XR-2]永恒(树链剖分,线段树) 题面 洛谷 题解 首先两个点的\(LCP\)就是\(Trie\)树上的\(LCA\)的深度. 考虑一对点的贡献,如果这两个点不具有祖先关系,那么这 ...

  4. LOJ 2339 「WC2018」通道——边分治+虚树

    题目:https://loj.ac/problem/2339 两棵树的话,可以用 CTSC2018 暴力写挂的方法,边分治+虚树.O(nlogn). 考虑怎么在这个方法上再加一棵树.发现很难弄. 看了 ...

  5. Bzoj1018/洛谷P4246 [SHOI2008]堵塞的交通(线段树分治+并查集)

    题面 Bzoj 洛谷 题解 考虑用并查集维护图的连通性,接着用线段树分治对每个修改进行分治. 具体来说,就是用一个时间轴表示图的状态,用线段树维护,对于一条边,我们判断如果他的存在时间正好在这个区间内 ...

  6. bzoj 3295 (洛谷3157、3193) [Cqoi2011]动态逆序对——树套树 / CDQ分治

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3295 题目--洛谷3157:https://www.luogu.org/problemnew ...

  7. 【洛谷4721】【模板】分治FFT(CDQ分治_NTT)

    题目: 洛谷 4721 分析: 我觉得这个 "分治 FFT " 不能算一种特殊的 FFT ,只是 CDQ 分治里套了个用 FFT (或 NTT)计算的过程,二者是并列关系而不是偏正 ...

  8. 【洛谷4219】[BJOI2014]大融合(线段树分治)

    题目: 洛谷4219 分析: 很明显,查询的是删掉某条边后两端点所在连通块大小的乘积. 有加边和删边,想到LCT.但是我不会用LCT查连通块大小啊.果断弃了 有加边和删边,还跟连通性有关,于是开始yy ...

  9. BZOJ4553/洛谷P4093 [HEOI2016/TJOI2016]序列 动态规划 分治

    原文链接http://www.cnblogs.com/zhouzhendong/p/8672434.html 题目传送门 - BZOJ4553 题目传送门 - 洛谷P4093 题解 设$Li$表示第$ ...

随机推荐

  1. 这样调优之后,单机也能扛下100W连接

    1 模拟单机连接瓶颈 我们知道,通常启动一个服务端会绑定一个端口,例如8000端口,当然客户端连接端口是有限制的,除去最大端口65535和默认的1024端口及以下的端口,就只剩下1 024~65 53 ...

  2. Elasticsearch 中为什么选择倒排索引而不选择 B 树索引

    目录 前言 为什么全文索引不使用 B+ 树进行存储 全文检索 正排索引 倒排索引 倒排索引如何存储数据 FOR 压缩 RBM 压缩 倒排索引如何存储 字典树(Tria Tree) FST FSM 构建 ...

  3. Java:并发笔记-07

    Java:并发笔记-07 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 6. 共享模型之不可变 本章内容 不可变类的使用 不可变类设计 无状态类设计 6.1 ...

  4. Spring Cloud Gateway GatewayFilter的使用

    Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...

  5. 微信小程序实现上拉和下拉加载更多

    在上一篇文章中,我们知道了使用 scroll-view 可以实现上拉加载更多,但是由于 scroll-view 的限制,它无法实现下拉加载更多,这篇文章我们使用 view 组件来实现 上拉和下拉加载更 ...

  6. 人人都写过的5个Bug!

    大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...

  7. Python课程笔记(十)

    不陌生,之前学习一个开源SpringBoot项目,Mysql5.5更换到5.7搞得头疼. 数据库连接的坑之前写的IDEA系列连接会遇到的问题.课程代码 今天上课就主要学习了python如何连接mysq ...

  8. DC综合与Tcl语法结构概述

    转载:https://www.cnblogs.com/IClearner/p/6617207.html 1.逻辑综合的概述 synthesis = translation + logic optimi ...

  9. 使用Visual Studio 2019将ASP.NET Core发布为linux-arm64程序

    前言 前段时间入手了一台树莓派4B,一直闲置未使用,最近工作需要,要在上面跑下.NET Core程序,由于树莓派4B使用的是ARM架构,并且支持64位操作系统,为了充分发挥树莓派性能,我的这台树莓派安 ...

  10. 什么?还在用delete删除数据《死磕MySQL系列 九》

    系列文章 五.如何选择普通索引和唯一索引<死磕MySQL系列 五> 六.五分钟,让你明白MySQL是怎么选择索引<死磕MySQL系列 六> 七.字符串可以这样加索引,你知吗?& ...