洛谷题面传送门

神仙题。

首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑。考虑每次找出 \(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. 【UE4 调试】C++ 几种编译方法和小技巧

    编译方法 Visual Studio 2019 编译 默认编译 UnrealVS 快速编译 Editor 编译 一般 vs 编译完后,Editor会跟着热编译(有声音) 如果发现编译后代码没更新到Ed ...

  2. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

  3. 实用小工具:screen

    实用小工具:screen 首先,吹爆screen screen,实现了不间断的会话服务,通过SSH连接至远程服务器,当使用了screen开启的会话,不会因为你断开SSH而中断在远程服务器上运行的命令. ...

  4. 王爽汇编第五章,[bx]和loop指令

    目录 王爽汇编第五章,[bx]和loop指令 [bx]和loop指令 例子: 王爽汇编第五章,[bx]和loop指令 [bx]和loop指令 [bx]之前我们介绍寄存器的时候,已经很详细的说明过了,b ...

  5. 纯 CSS 自定义多行省略:从原理到实现

    文字溢出怎么展示,你的需求是什么?单行还是多行?截断,省略,自定义样式,自适应高度?在这里你都能找到答案.接下来我会由浅入深,从原理到实现,带你一步步揭开多行省略的面纱.我们先从最简单的单行溢出省略开 ...

  6. RPC 框架 Dubbo 从理解到使用(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:RPC 框架 Dubbo 从理解到使用(一) 本篇文章讲解 Dubbo 支持的注册中心.Dubbo 负载均衡策略和 Dubbo 控制台的安装. 注册中心 ...

  7. C++ 内存四区 理解总结

    内存模型图(4G) 整体简单说明 32位CPU可寻址4G线性空间,每个进程都有各自独立的4G逻辑地址,其中 03G是用户空间**,**34G是内核空间即3G用户空间和1G内核空间,不同进程相同的逻辑地 ...

  8. vue禁用浏览器回退

    解决方案 mounted() { history.pushState(null, null, document.URL) window.addEventListener('popstate', () ...

  9. java中将double保留两位小数,将double保留两位小数并转换成String

    将Double类型的数据保留2位小数: Double a = 3.566; BigDecimal bd = new BigDecimal(a); Double d = bd.setScale(2, B ...

  10. dotnet templating 定制自己的项目模板

    由于工作需要,研究了一下VS 项目模板生成的相关内容,本文做一下记录借助.NET Core Template Engine创建一个加单的项目模板. 创建项目代码和配置文件 首先创建一个Minimal ...