题目
题目链接:https://www.luogu.com.cn/problem/P6199
妖怪之山上有 (n) 个地点,鸦天狗和河童的两套交通线路都是分别由一些连接这些地点的无向道路组成的,每条道路有自己的长度,把这些道路看成有权边,那么他们的两套线路分别可以表示成两棵 (n) 个节点的树 (T_1, T_2)(有 (n-1) 条边的有边权无向连通图),河童重工现在要新建一些无向道路,新建一条无向道路 ((i,j)) 的花费是 (dist_{T_1}(i,j)+dist_{T_2}(i,j))(即 (T_1,T_2) 上 (i) 到 (j) 的距离之和),河童重工要保证妖怪之山的任意两个节点都能只通过新修建的道路互相到达。但由于如果花费过多可能引起异变,所以他们希望这个工程的总花费最少。
荷取作为这个工程的总设计师,请你帮她算一算,修建新道路的总花费最少是多少?
(2 leq n leq 10^5)。
思路
先在 T1 中求出所有点到 (1) 的距离 (mathrm{dep1}_x)。用 ST 表预处理出 T1 的 LCA。
把 T2 点分治。求出每一个点到分治根的路径长度 (mathrm{dep2}_x)。考虑经过当前根节点的路径。
把当前 T2 点分治到的子数的点在 T1 中建立虚树。然后对于 T2 点分治到的点 (x),我们在虚树中给它拆出一个点 (x'),从 (x') 向 (x) 连一条长度为 (mathrm{dep2}_x) 的边。然后对于虚树上的一条边 ((x,y)),连一条长度为 T1 中 (x,y) 距离的边。事实上由于虚树中的相邻的点在 T1 中一定是祖孙关系,所以直接 (mathrm{dep1}) 减一下即可。
然后我们从所有拆出来的点 (x') 跑最短路,求出虚树上所有点到达最近的 (x') 的距离 (mathrm{dis}) 和最近的 (x') 的编号 (mathrm{pre})。接下来枚举虚树上的每一条边 ((x,y)),向边集 (E) 中扔一条连接 (mathrm{pre}_x,mathrm{pre}_y),长度为 (mathrm{dis_x}+mathrm{dis_y}+d_{x,y}),其中 (d_{x,y}) 就是虚树中 (x,y) 边的长度(也就是 T1 中 (x,y) 的距离)。
显然我们扔进 (E) 的边的长度一定不小于实际长度(也就是我们计算的两个点的距离不小于实际上它们在 T1 的距离加上 T2 的距离)。所以我们只需要证明所有最终会产生贡献的边一定在 (E) 中且长度一定等于实际长度即可。
考虑最终被连边的两个点 (x,y),它们一定会同时在某一棵虚树中,且虚树上它们之间路径的 (mathrm{pre}) 一定是一段前缀为 (x),另一段后缀为 (y),否则假设其路径中有一个点的 (mathrm{pre}) 为点 (z),显然最终的生成树中 ((x,y)) 没有 ((x,z),(z,y)) 优。
而且不难发现当分治到的根为 T2 中 (x) 到 (y) 的路径时,我们扔进 (E) 的路径 ((x,y)) 长度就是正确的。
由于一个大小为 (k) 的点分树,我们扔进 (E) 的边数是 (O(k)) 的,所以最终 (E) 中的边的数量就会是 (O(nlog n)) 的,跑 Kruskal 即可。
时间复杂度 (O(nlog^2 n))。
代码
借用 Fellyhosn 智慧珠游戏题解 中的一句话:
代码不长,仅三百行。没有猪国杀的变态,没有 mayan 游戏的玄学,没有 A+B 问题的变幻无常,没有任何超纲。阻挡你向前的只是你的懒惰,是你的习得性无助。
#include <bits/stdc++.h>
#define mp make_pair
#define int long long
using namespace std;
typedef long long ll;
const int N=600010,LG=20,Inf=1e9;
int n,rt,ans;
struct edge1
{
int next,to,dis;
};
struct edge2
{
int from,to,dis;
};
bool cmp1(edge2 x,edge2 y)
{
return x.dis<y.dis;
}
struct Kruskal
{
int tot,father[N*LG];
edge2 e[N*LG];
void add(int from,int to,int dis)
{
e[++tot]=(edge2){from,to,dis};
}
int find(int x)
{
return x==father[x]?x:father[x]=find(father[x]);
}
void kruskal()
{
sort(e+1,e+1+tot,cmp1);
for (int i=1;i<=n;i++)
father[i]=i;
for (int i=1;i<=tot;i++)
{
int u=find(e[i].to),v=find(e[i].from);
if (u!=v)
{
father[u]=v;
ans+=e[i].dis;
}
}
}
}t4;
struct Tree1
{
int tot,head[N],dep[N],dfn[N],lg[N],st[N][LG+1];
edge1 e[N];
void add(int from,int to,int dis)
{
e[++tot]=(edge1){head[from],to,dis};
head[from]=tot;
}
void dfs1(int x,int fa)
{
st[++tot][0]=x; dfn[x]=tot;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
dep[v]=dep[x]+e[i].dis;
dfs1(v,x);
st[++tot][0]=x;
}
}
}
void buildst()
{
for (int i=2;i<=tot;i++)
lg[i]=lg[i>>1]+1;
for (int i=tot;i>=1;i--)
for (int j=1;i+(1<<j)-1<=tot;j++)
if (dep[st[i][j-1]]<dep[st[i+(1<<j-1)][j-1]])
st[i][j]=st[i][j-1];
else
st[i][j]=st[i+(1<<j-1)][j-1];
}
int lca(int x,int y)
{
x=dfn[x]; y=dfn[y];
if (x>y) swap(x,y);
int k=lg[y-x+1];
if (dep[st[x][k]]<dep[st[y-(1<<k)+1][k]])
return st[x][k];
else
return st[y-(1<<k)+1][k];
}
}t1;
bool cmp(int x,int y)
{
return t1.dfn[x]<t1.dfn[y];
}
struct Tree3
{
int tot,cnt,top,q[N],head[N],st[N],pre[N],dis[N];
bool vis[N];
edge1 e[N];
void clr()
{
for (int i=0;i<=max(cnt,tot);i++)
{
q[i]=st[i]=0;
head[e[i].to]=-1;
}
cnt=tot=top=0;
}
void add(int from,int to,int dis)
{
e[++tot]=(edge1){head[from],to,dis};
head[from]=tot;
}
void build()
{
sort(q+1,q+1+cnt,cmp);
st[1]=q[1]; top=1;
for (int i=2;i<=cnt;i++)
{
int p=t1.lca(st[top],q[i]);
for (;top>1 && t1.dep[p]<=t1.dep[st[top-1]];top--)
{
add(st[top],st[top-1],t1.dep[st[top]]-t1.dep[st[top-1]]);
add(st[top-1],st[top],t1.dep[st[top]]-t1.dep[st[top-1]]);
}
if (top && p!=st[top])
{
add(st[top],p,t1.dep[st[top]]-t1.dep[p]);
add(p,st[top],t1.dep[st[top]]-t1.dep[p]);
st[top]=p;
}
st[++top]=q[i];
}
for (;top>1;top--)
{
add(st[top],st[top-1],t1.dep[st[top]]-t1.dep[st[top-1]]);
add(st[top-1],st[top],t1.dep[st[top]]-t1.dep[st[top-1]]);
}
}
void dij()
{
priority_queue<pair<int,int> > Q;
for (int i=1;i<=tot;i++)
if (e[i].to>n)
Q.push(mp(0,e[i].to)),pre[e[i].to]=e[i].to-n,dis[e[i].to]=0,vis[e[i].to]=0;
else
dis[e[i].to]=Inf,vis[e[i].to]=0;
while (Q.size())
{
int u=Q.top().second; Q.pop();
if (vis[u]) continue;
vis[u]=1;
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
pre[v]=pre[u];
Q.push(mp(-dis[v],v));
}
}
}
}
void addedge()
{
for (int i=1;i<=tot;i+=2)
{
int u=e[i].to,v=e[i+1].to;
if (pre[u]==pre[v]) continue;
t4.add(pre[u],pre[v],dis[u]+dis[v]+e[i].dis);
}
}
}t3;
struct Tree2
{
int tot,head[N],dep[N],siz[N],maxp[N];
bool vis[N];
edge1 e[N];
void add(int from,int to,int dis)
{
e[++tot]=(edge1){head[from],to,dis};
head[from]=tot;
}
void dfs2(int x,int fa)
{
t3.q[++t3.cnt]=x;
t3.add(x,n+x,dep[x]); t3.add(n+x,x,dep[x]);
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa && !vis[v])
{
dep[v]=dep[x]+e[i].dis;
dfs2(v,x);
}
}
}
void findrt(int x,int fa,int sum)
{
siz[x]=1; maxp[x]=0;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa && !vis[v])
{
findrt(v,x,sum);
siz[x]+=siz[v];
if (siz[v]>maxp[x]) maxp[x]=siz[v];
}
}
if (sum-maxp[x]>maxp[x]) maxp[x]=sum-maxp[x];
if (maxp[x]<maxp[rt] || !rt) rt=x;
}
void calc(int x)
{
t3.clr();
dep[x]=0; dfs2(x,0);
t3.build(); t3.dij();
t3.addedge();
}
void dfs1(int x,int sum)
{
calc(x);
vis[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (!vis[v])
{
int s=(siz[v]<siz[x])?siz[v]:sum-siz[x];
rt=0; findrt(v,x,s);
dfs1(rt,s);
}
}
}
}t2;
signed main()
{
memset(t1.head,-1,sizeof(t1.head));
memset(t2.head,-1,sizeof(t2.head));
memset(t3.head,-1,sizeof(t3.head));
scanf("%lld",&n);
for (int i=1,x,y,z;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
t1.add(x,y,z); t1.add(y,x,z);
}
for (int i=1,x,y,z;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
t2.add(x,y,z); t2.add(y,x,z);
}
t1.tot=0;
t1.dfs1(1,0); t1.buildst();
rt=0; t2.findrt(1,0,n);
t2.dfs1(rt,n);
t4.kruskal();
printf("%lld
",ans);
return 0;
}