原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ347.html

题意

  有三棵树,边有边权。

  对于所有点对 (x,y) 求在三棵树上 x 到 y 的距离之和 的最大值。

  点数 <=100000

题解

  我自闭了。

  在此之前,我没写过边分治,只写过一次虚树。

  我自闭了。

  一棵树怎么做?

  树的直径。

  两棵树怎么做?

  有一个定理:从点集A中的点到点集B中的点的最长路径的两端点一定属于   点集A中最长路两端点和点集B中最长路两端点  构成的集合。

  首先,在第一棵树上,我们求出每一个点的深度(即到根距离),记点 x 的深度为 D[x] 。则点 x 到 点 y 在这两棵树上的距离之和为 D[x]+D[y]-2D[LCA(x,y)] + dis(x,y) 。(其中dis(x,y) 代表在第二棵树上的距离)

  我们考虑在第二棵树上,对于任意一个点 x ,新建节点 x' , x' 仅和 x 有一条权值为 D[x] 的边。那么 x 到 y 的距离就是 dis(x',y') - 2D[LCA(x,y)] 。我们考虑对第一棵树进行dfs,对于每一个节点,依次将子树点集中的最远点对合并到父亲上来(这里要用到之前说的定理),顺便更新答案即可。

  那么三棵树呢?

  给多出来的那棵树边分治一下,将第三棵树中的边 (x,x') 的权值修改成 D[x] + 在第三棵树中 x 到边分中心的距离。注意边分的时候作为边分中心的那条边的权值不要忘记加上。

  对于第三棵树中分治出来的点集,我们需要在第二棵树上建虚树。

  实际写代码的时候,节点 x' 以及边 (x,x') 都是不用加上的。

代码

#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
using namespace std;
typedef long long LL;
LL read(){
LL x=0,f=0;
char ch=getchar();
while (!isdigit(ch))
f|=ch=='-',ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=100005*2;
const LL INF=5e17;
int n,m;
struct Gragh{
static const int M=N*2;
int cnt,y[M],nxt[M],fst[N];
LL z[M];
void clear(){
cnt=1;
memset(fst,0,sizeof fst);
}
void add(int a,int b,LL c){
y[++cnt]=b,z[cnt]=c,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g[4],G;
#define For(i,y,g,x) for (LL i=g.fst[x],y=g.y[i];i;i=g.nxt[i],y=g.y[i])
#define Foryx(y,g,x) For(_index,y,g,x)
#define Fory(y,g) Foryx(y,g,x)
#define Forg(g) Fory(y,g)
int fa[4][N][17],depth[4][N];
LL len[4][N],addv[N];
int I[N],O[N],_Time=0;
void dfs1(int _id,int x,int pre,int d,LL L){
fa[_id][x][0]=pre;
for (int i=1;i<17;i++)
fa[_id][x][i]=fa[_id][fa[_id][x][i-1]][i-1];
depth[_id][x]=d,len[_id][x]=L;
Forg(g[_id])
if (y!=pre)
dfs1(_id,y,x,d+1,L+g[_id].z[_index]);
}
void dfs2(int x,int pre){
I[x]=++_Time;
Forg(g[2])
if (y!=pre)
dfs2(y,x);
O[x]=_Time;
}
int LCA(int id,int x,int y){
if (depth[id][x]<depth[id][y])
swap(x,y);
for (int i=16;i>=0;i--)
if (depth[id][x]-(1<<i)>=depth[id][y])
x=fa[id][x][i];
if (x==y)
return x;
for (int i=16;i>=0;i--)
if (fa[id][x][i]!=fa[id][y][i])
x=fa[id][x][i],y=fa[id][y][i];
return fa[id][x][0];
}
LL Dis(int id,int x,int y){
return len[id][x]+len[id][y]-len[id][LCA(id,x,y)]*2;
}
void dfs3(Gragh &g1,Gragh &g2,int x,int pre){
int p=x;
Forg(g1)
if (y!=pre){
m++;
g2.add(p,m,0),g2.add(m,p,0);
g2.add(y,m,g1.z[_index]);
g2.add(m,y,g1.z[_index]);
p=m;
}
Forg(g1)
if (y!=pre)
dfs3(g1,g2,y,x);
}
void rebuild(Gragh &g,Gragh &res){
res.clear(),m=n;
dfs3(g,res,1,0);
}
int vis[N],size[N],Size,ckv[N];
int RT,RTF,Time=0;
LL LEN;
vector <int> node;
void dfs4(int x,int pre){
if (x<=n)
node.push_back(x);
size[x]=1;
Forg(g[0])
if (y!=pre&&!vis[y])
dfs4(y,x),size[x]+=size[y];
ckv[x]=max(size[x],Size-size[x]);
if (!RT||ckv[x]<ckv[RT])
RT=x,RTF=pre;
}
int tag[N];
LL ans;
void dfs5(int x,int pre,int Tag,LL D){
tag[x]=Tag,addv[x]=D;
Forg(g[0])
if (y!=pre&&!vis[y])
dfs5(y,x,Tag,D+g[0].z[_index]);
}
LL SpDis(int a,int b){
if (!a||!b)
return -INF;
return Dis(3,a,b)+addv[a]+addv[b];
}
bool cmpI(int x,int y){
return I[x]<I[y];
}
bool isfather(int x,int y){//x is father of y
return I[x]<=I[y]&&I[y]<=O[x];
}
struct road{
int x,y;
LL len;
road(){}
road(int _x,int _y,LL _len){
x=_x,y=_y,len=_len;
}
}r0[N],r1[N];
void Updr(road &a,road b){
LL d1=a.len,d2=SpDis(a.x,b.x),d3=SpDis(a.x,b.y);
LL d4=b.len,d5=SpDis(a.y,b.x),d6=SpDis(a.y,b.y);
LL mx=max(d1,max(d2,max(d3,max(d4,max(d5,d6)))));
if (d1==mx)
a=road(a.x,a.y,d1);
else if (d2==mx)
a=road(a.x,b.x,d2);
else if (d3==mx)
a=road(a.x,b.y,d3);
else if (d4==mx)
a=road(b.x,b.y,d4);
else if (d5==mx)
a=road(a.y,b.x,d5);
else
a=road(a.y,b.y,d6);
}
void Upda(road a,road b,LL Add){
ans=max(ans,SpDis(a.x,b.x)+Add);
ans=max(ans,SpDis(a.x,b.y)+Add);
ans=max(ans,SpDis(a.y,b.x)+Add);
ans=max(ans,SpDis(a.y,b.y)+Add);
}
void solve3(int x,int pre,LL D,LL Addlen){
addv[x]+=D,r0[x]=r1[x]=road(0,0,-INF);
if (tag[x]==-Time)
Updr(r0[x],road(x,x,0));
if (tag[x]==Time)
Updr(r1[x],road(x,x,0));
Forg(G){
solve3(y,x,D+G.z[_index],Addlen);
Upda(r0[x],r1[y],Addlen-D*2);
Upda(r1[x],r0[y],Addlen-D*2);
Updr(r0[x],r0[y]);
Updr(r1[x],r1[y]);
}
}
void make_tree(LL Addlen){
static int st[N],top;
top=st[0]=0;
if (abs(tag[1])!=Time)
node.push_back(1);
sort(node.begin(),node.end(),cmpI);
G.cnt=1;
for (auto x : node){
if (top>0&&!isfather(st[top],x)){
while (top>1&&!isfather(st[top-1],x))
G.add(st[top-1],st[top],len[2][st[top]]-len[2][st[top-1]]),top--;
int y=LCA(2,st[top],x);
if (y!=st[top-1])
G.fst[y]=0;
G.add(y,st[top],len[2][st[top]]-len[2][y]);
top--;
if (y!=st[top])
st[++top]=y;
}
G.fst[x]=0,st[++top]=x;
}
for (int i=1;i<top;i++)
G.add(st[i],st[i+1],len[2][st[i+1]]-len[2][st[i]]);
solve3(1,0,0,Addlen);
}
void solve(int _x){
if (Size==1)
return;
Time++,RT=RTF=0;
node.clear();
dfs4(_x,0);
int x=RT,y=RTF;
dfs5(x,y,Time,0);
dfs5(y,x,-Time,0);
for (int i=g[0].fst[x];i;i=g[0].nxt[i])
if (g[0].y[i]==y){
LEN=g[0].z[i];
break;
}
make_tree(LEN);
int sz1=size[x],sz2=Size-sz1;
vis[y]=1,Size=sz1,solve(x),vis[y]=0;
vis[x]=1,Size=sz2,solve(y),vis[x]=0;
}
int main(){
n=read();
for (int t=1;t<=3;t++){
g[t].clear();
for (int i=1;i<n;i++){
int x=read(),y=read();
LL z=read();
g[t].add(x,y,z);
g[t].add(y,x,z);
}
}
dfs1(2,1,0,0,0);
dfs1(3,1,0,0,0);
dfs2(1,0);
clr(addv),clr(vis);
rebuild(g[1],g[0]);
Size=m,ans=0;
solve(1);
cout<<ans<<endl;
return 0;
}

  

UOJ#347. 【WC2018】通道 边分治 虚树的更多相关文章

  1. [WC2018]通道——边分治+虚树+树形DP

    题目链接: [WC2018]通道 题目大意:给出三棵n个节点结构不同的树,边有边权,要求找出一个点对(a,b)使三棵树上这两点的路径权值和最大,一条路径权值为路径上所有边的边权和. 我们按照部分分逐个 ...

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

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

  3. 【UOJ347】【WC2018】通道 边分治 虚树 DP

    题目大意 给你三棵树,点数都是\(n\).求 \[ \max_{i,j}d_1(i,j)+d_2(i,j)+d_3(i,j) \] 其中\(d_k(i,j)\)是在第\(k\)棵数中\(i,j\)两点 ...

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

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

  5. UOJ347 WC2018 通道 边分治、虚树

    传送门 毒瘤数据结构题qwq 设三棵树分别为$T1,T2,T3$ 先将$T1$边分治,具体步骤如下: ①多叉树->二叉树,具体操作是对于每一个父亲,建立与儿子个数相同的虚点,将父亲与这些虚点穿成 ...

  6. BZOJ5341[Ctsc2018]暴力写挂——边分治+虚树+树形DP

    题目链接: CSTC2018暴力写挂 题目大意:给出n个点结构不同的两棵树,边有边权(有负权边及0边),要求找到一个点对(a,b)满足dep(a)+dep(b)-dep(lca)-dep'(lca)最 ...

  7. LOJ 2553 「CTSC2018」暴力写挂——边分治+虚树

    题目:https://loj.ac/problem/2553 第一棵树上的贡献就是链并,转化成 ( dep[ x ] + dep[ y ] + dis( x, y ) ) / 2 ,就可以在第一棵树上 ...

  8. 【XSY3350】svisor - 点分治+虚树dp

    题目来源:NOI2019模拟测试赛(九) 题意: 吐槽: 第一眼看到题觉得这不是震波的完全弱化版吗……然后开开心心的码了个点分治 码到一半突然发现看错题了……心态崩了于是就弃疗手玩提答去了 于是就快乐 ...

  9. 洛谷 P6199 - [EER1]河童重工(点分治+虚树)

    洛谷题面传送门 神仙题. 首先看到这样两棵树的题目,我们肯定会往动态树分治的方向考虑.考虑每次找出 \(T_2\) 的重心进行点分治.然后考虑跨过分治中心的点对之间的连边情况.由于连边边权与两棵树都有 ...

随机推荐

  1. [WC2018]通道——边分治+虚树+树形DP

    题目链接: [WC2018]通道 题目大意:给出三棵n个节点结构不同的树,边有边权,要求找出一个点对(a,b)使三棵树上这两点的路径权值和最大,一条路径权值为路径上所有边的边权和. 我们按照部分分逐个 ...

  2. mpvue——引入vant_weapp组件

    克隆仓库 克隆后,将dist目录下的所有文件复制到项目中的/static/vant/目录下,vant目录是我自己创建为了区分的 git clone https://github.com/youzan/ ...

  3. Hdoj 1850.Being a Good Boy in Spring Festival 题解

    Problem Description 一年在外 父母时刻牵挂 春节回家 你能做几天好孩子吗 寒假里尝试做做下面的事情吧 陪妈妈逛一次菜场 悄悄给爸爸买个小礼物 主动地 强烈地 要求洗一次碗 某一天早 ...

  4. PEP8中文翻译(转)

    原文:https://github.com/zgia/manual PEP 8 -- Style Guide for Python Code PEP Index > PEP 8 -- Style ...

  5. kubernetes云平台管理实战:HPA水平自动伸缩(十一)

    一.自动伸缩 1.启动 [root@k8s-master ~]# kubectl autoscale deployment nginx-deployment --max=8 --min=2 --cpu ...

  6. Fiddler--Filters

    本篇主要介绍Fiddler中Filters(过滤器)选项功能. 先看看Filters的界面: 一.模块一 当勾选“Use Filters”,Filters才开始工作:否则Filters中的设置内容将无 ...

  7. 一个select元素自定义设计的新思路:appearance: none之后利用<>符号制造小箭头

    最近工作时解决了一个前端小问题(如下图所示):在Safari中,select的控件之上有不和谐的灰色部分. 刚开始时我以为是backgrand或是border设置不当之类产生的问题,在搜索了很久之后终 ...

  8. Typora使用说明(记录总结)

    目录 区域元素 YAML FONT Matters 菜单 段落 标题 引注 序列 可选序列 代码块 数学块 表格 脚注 水平线 特征元素 链接 超链接 内链接 相关链 URLs 图片 斜体 加粗 删除 ...

  9. 第七节:Trigger(SimpleTrigger、CronTrigger)哑火(MisFire)策略 :

    一. 简介 1. 什么是哑火 由于某些原因导致触发器(trigger)在该触发的时候没有得到触发,后续对应的解决策略即为哑火策略.(个人理解) 2. 哑火触发的条件 ①:所有的工作线程都在忙碌,导致某 ...

  10. JavaScript null和undefined的区别

    前言 1995年javascript诞生时,最初像Java一样,只设置了null作为表示"无"的值.根据C语言的传统,null被设计成可以自动转为0 但是,javascript的设 ...