题面

传送门

题解

代码不就百来行么也不算很长丫

虽然这题随机化贪心就可以过而且速度和正解差不多不过我们还是要好好学正解

前置芝士

边分治

米娜应该都知道点分治是个什么东西,而边分治,顾名思义就是对边进行分治,即每次选出一条“子树中点的个数的最大值最小”的边,处理所有经过这条边的路径的贡献,然后割掉这条边之后对子树递归下去就好了

然而出题人给你一个菊花图就能把你卡得不要不要的

我们发现上述策略在一个二叉树上是最优的,因为割掉边之后左右子树大小都会变为原来的一半

于是这里就需要多叉树转二叉树,具体的流程可以看\(shadowice\)巨巨的图

这样的话点数最多变成\(2n\),但是分治的时候复杂度就有保证了

虚树

咱对这东西也不熟所以请米娜自己去找吧

本题

本题要求我们最大化

\[dis1(x,y)+dis2(x,y)+dis3(x,y)
\]

我们在第一棵树上边分,处理所有经过\((u,v)\)这条边的路径的贡献,那么肯定满足\(x\)和\(u\)在同一联通块(以下称为黑点),\(y\)和\(v\)在同一个联通块(以下称为白点),并且因为\((u,v)\)的长度是一个定值,那么我们需要最大化

\[dis1(x,u)+dis1(v,y)+dis2(x,y)+dis3(x,y)
\]

把它改写一下

\[dis1(x,u)+dis1(v,y)+dep2(x)+dep2(y)+dep3(x)+dep3(y)
\]

上面的柿子里是只和\(x,y\)有关的,那么我们还需要减去

\[2\times dep2(LCA2(x,y))+2\times dep3(LCA3(x,y))
\]

因为我们已经选定了\(u,v\),所以我们可以快速预处理出\(w(x)=dis1(x,u/v)+dep2(x)+dep3(x)\),那么就变成最大化

\[w(x)+w(y)-2\times dep2(LCA2(x,y))-2\times dep3(LCA3(x,y))
\]

发现每一次分治的时候满足条件的\(x,y\)都是一个点集,那么我们可以把这些点集放到第二颗树上做一个虚树

在虚树上,我们可以枚举\(LCA\),设为\(p\),那么此时\(dep2(LCA2(x,y))\)就是个定值了,我们需要求出两个点\(x,y\),其中\(x\)是黑点,\(y\)是白点,且\(x,y\)不在\(p\)的同一子树内,并且最大化

\[w(x)+w(y)-2\times dep3(LCA3(x,y))
\]

首先你需要知道一个结论:如果有一棵树\(T\),边权非负,直径的端点为\(u,v\),和一棵树\(G\),直径的端点为\(a,b\),那么用一条边将它们连接起来之后(无论把两棵树的那两个节点相连),直径一定为\(uv,ua,ub,va,vb,ab\)六条路径中的一条(这里的直径定义为不存在路径比它长,不过有可能有路径和它长度相等)

证明的话可以感性理解一下

然后这里就需要用到一个推论:对于两个树上的点集\(A,B\),\(A\)的直径端点为\(u,v\),\(B\)的直径端点为\(a,b\),那么一条左右端点分别在\(A,B\)中的最长的路径必为\(ua,uv,va,vb\)四条路径中的一条

然而放到这里有什么用么?\(w(x)+w(y)-2\times dep3(LCA3(x,y))\)怎么看也不像是树上两点的距离啊?

那么我们把它化一下,变成

\[dis1(x,u)+dep2(x)+dis2(y,v)+dep2(y)+dis3(x,y)
\]

那么我们可以看做是在第三棵树上,在\(x\)下面挂一个虚点\(x'\),\((x,x')\)的长度为\(dis1(x,u)+dep2(x)\),\(y\)同理,这样能看做是树上的一条路径了,也就是说我们关于树上路径的那个结论成立

然后就没有然后了,码吧

似乎有大佬写了随机化贪心的做法,这里说一下好了,就是每一次枚举根节点算出三棵树上其他点到它距离总和的最大值,用距离最大的点来更新根节点,然后每隔几次之后重新\(rand\)根节点……我调了调之后速度已经和我的正解差不多了(虽然会被\(uoj\)上的\(hack\)数据卡掉不过比赛的时候可没有这么强的数据啊)

虽然我估计考场上我可能这种随机化贪心都打不出来

可以对比一下下面两份代码的码量……

这份是随机化贪心,洛谷上能过,\(uoj\)会被\(hack\)

// luogu-judger-enable-o2
//minamoto
#include<bits/stdc++.h>
#define R register
#define ll long long
#define CL 1000.0*clock()/CLOCKS_PER_SEC
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
ll read(){
R ll res,f=1;R char ch;
while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
return res*f;
}
const int N=1e5+5;
struct eg{int v,nx;ll w;};
struct Gr{
eg e[N<<1];int head[N],tot;
inline void add(R int u,R int v,R ll w){e[++tot]={v,head[u],w},head[u]=tot;}
ll dis[N];
void dfs(int u,int fa){go(u)v!=fa?(dis[v]=dis[u]+e[i].w,dfs(v,u),0):0;}
}g[3];
ll res,w,las;bool vis[N];int n,u,v,c;
inline int rd(){return 1ll*rand()*rand()%n+1;}
void qwq(){
fp(rt,1,n){
g[0].dis[rt]=g[1].dis[rt]=g[2].dis[rt]=0;
g[0].dfs(rt,0),g[1].dfs(rt,0),g[2].dfs(rt,0);
fp(j,1,n)cmax(res,g[0].dis[j]+g[1].dis[j]+g[2].dis[j]);
}
printf("%lld\n",res);
}
int main(){
srand(19260817);
// freopen("testdata.in","r",stdin);
double st=CL;
n=read();
fp(i,1,n-1)u=read(),v=read(),w=read(),g[0].add(u,v,w),g[0].add(v,u,w);
fp(i,1,n-1)u=read(),v=read(),w=read(),g[1].add(u,v,w),g[1].add(v,u,w);
fp(i,1,n-1)u=read(),v=read(),w=read(),g[2].add(u,v,w),g[2].add(v,u,w);
if(n<=3500)return qwq(),0;
while(CL-st<=3400){
int rt=rd();las=res;
while(vis[rt])rt=rd();
fp(i,1,5){
if(vis[rt])break;
vis[rt]=1,g[0].dis[rt]=g[1].dis[rt]=g[2].dis[rt]=0;
g[0].dfs(rt,0),g[1].dfs(rt,0),g[2].dfs(rt,0);
ll mx=0;
fp(j,1,n)
cmax(res,g[0].dis[j]+g[1].dis[j]+g[2].dis[j]),
!vis[j]&&cmax(mx,g[0].dis[j]+g[1].dis[j]+g[2].dis[j])?rt=j:0;
}
las==res?++c:c=0;
if(c>=20)break;
}
printf("%lld\n",res);
return 0;
}

然后下面这份是正解

虽然它们都说求\(LCA\)要用\(ST\)表然而亲测用树剖似乎速度差不多?

// luogu-judger-enable-o2
//minamoto
#include<bits/stdc++.h>
#define R register
#define ll long long
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
ll read(){
R ll res,f=1;R char ch;
while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
return res*f;
}
const int N=5e5+5;
int lg[N],s[2][N],top[2],ty[N];ll w[N],res;
int n;
namespace tree3{
struct eg{int v,nx;ll w;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v,R ll w){e[++tot]={v,head[u],w},head[u]=tot;}
int top[N],dep[N],fa[N],sz[N],son[N];ll dis[N];
void dfs1(int u){
sz[u]=1,dep[u]=dep[fa[u]]+1;
go(u)if(v!=fa[u]){
dis[v]=dis[u]+e[i].w,fa[v]=u,dfs1(v),sz[u]+=sz[v];
sz[v]>sz[son[u]]?son[u]=v:0;
}
}
void dfs2(int u,int t){
top[u]=t;if(!son[u])return;
dfs2(son[u],t);
go(u)!top[v]?(dfs2(v,v),0):0;
}
inline int LCA(R int u,R int v){
while(top[u]!=top[v]){
dep[top[u]]<dep[top[v]]?(swap(u,v),0):0;
u=fa[top[u]];
}return dep[u]<dep[v]?u:v;
}
inline ll Dis(R int u,R int v){return w[u]+w[v]+dis[u]+dis[v]-(dis[LCA(u,v)]<<1);}
}
namespace tree2{
struct eg{int v,nx;ll w;}e[N<<1];int head[N],tot;
inline void add(R int u,R int v,R ll w){e[++tot]={v,head[u],w},head[u]=tot;}
int top[N],dep[N],fa[N],sz[N],son[N],low[N],dfn[N],cnt;ll dis[N];
void dfs1(int u){
sz[u]=1,dep[u]=dep[fa[u]]+1,dfn[u]=++cnt;
go(u)if(v!=fa[u]){
dis[v]=dis[u]+e[i].w,fa[v]=u,dfs1(v),sz[u]+=sz[v];
sz[v]>sz[son[u]]?son[u]=v:0;
}
low[u]=cnt;
}
void dfs2(int u,int t){
top[u]=t;if(!son[u])return;
dfs2(son[u],t);
go(u)!top[v]?(dfs2(v,v),0):0;
}
inline int LCA(R int u,R int v){
while(top[u]!=top[v]){
dep[top[u]]<dep[top[v]]?(swap(u,v),0):0;
u=fa[top[u]];
}return dep[u]<dep[v]?u:v;
}
inline void Pre(){fp(i,1,n)head[i]=0;tot=0;}
struct node{
int u,v;ll d;
node():u(0),v(0),d(0){}
node(R int uu,R int vv,R ll dd):u(uu),v(vv),d(dd){}
node(R int uu,R int vv):u(uu),v(vv){d=tree3::Dis(u,v);}
inline bool operator <(const node &b)const{return !u?1:!b.u?0:d<b.d;}
inline node operator +(const node &b)const{
node res=max(*this,b);
cmax(res,max(node(u,b.u),node(u,b.v))),
cmax(res,max(node(v,b.u),node(v,b.v)));
return res;
}
inline ll merge(const node &b){
return max(max(node(u,b.u),node(u,b.v)),max(node(v,b.u),node(v,b.v))).d;
}
}f[N][2];
int q[N<<2],stack[N<<1],t;bool vis[N],ins[N];ll tag;
inline bool cmp(const int &x,const int &y){return dfn[x]<dfn[y];}
void dp(int u){
f[u][0]=f[u][1]=node();
vis[u]?(f[u][ty[u]]=node(u,u,0),0):0;
go(u){
dp(v),
cmax(res,max(f[u][0].merge(f[v][1]),f[u][1].merge(f[v][0]))+tag-(dis[u]<<1)),
f[u][0]=f[u][0]+f[v][0],
f[u][1]=f[u][1]+f[v][1];
}
}
void solve(ll val){
tag=val,t=0,tot=0;
fp(i,1,::top[0])q[++t]=s[0][i];
fp(i,1,::top[1])q[++t]=s[1][i];
sort(q+1,q+1+t,cmp);
fp(i,1,t)vis[q[i]]=ins[q[i]]=1,w[q[i]]+=dis[q[i]];
for(R int i=t,lca;i>1;--i)lca=LCA(q[i],q[i-1]),!ins[lca]?(q[++t]=lca,ins[lca]=1):0;
sort(q+1,q+1+t,cmp);
for(R int i=1,top=0;i<=t;++i){
while(top&&low[stack[top]]<dfn[q[i]])--top;
if(top)add(stack[top],q[i],dis[q[i]]-dis[stack[top]]);
stack[++top]=q[i];
}
dp(q[1]);
fp(i,1,t)vis[q[i]]?w[q[i]]-=dis[q[i]]:0,vis[q[i]]=ins[q[i]]=head[q[i]]=0;
}
}
namespace tree1{
struct eg{int v,nx;ll w;}e[N<<2];int head[N<<1],tot=1;
inline void add(R int u,R int v,R ll w){e[++tot]={v,head[u],w},head[u]=tot;}
int cnt;
namespace qwq{
struct eg{int v,nx;ll w;}e[N<<1];int head[N],las[N],tot;
inline void add(R int u,R int v,R ll w){e[++tot]={v,head[u],w},head[u]=tot;}
void ins(int u,int v,ll w){
++cnt,tree1::add(cnt,v,w),tree1::add(v,cnt,w),
tree1::add(las[u],cnt,0),tree1::add(cnt,las[u],0),
las[u]=cnt;
}
void build(int u,int fa){go(u)if(v!=fa)ins(u,v,e[i].w),build(v,u);}
inline void init(){fp(i,1,n)las[i]=i;cnt=n;build(1,0);}
}
bool vis[N];int sz[N],rt,mx;ll dis[N];
void findrt(int u,int fa,int size){
sz[u]=1;
go(u)if(!vis[i>>1]&&v!=fa){
findrt(v,u,size),sz[u]+=sz[v];
cmin(mx,max(sz[v],size-sz[v]))?rt=i:0;
}
}
void dfs(int u,int fa,int op){
u<=n?ty[u]=op,s[op][++top[op]]=u:0;
go(u)if(v!=fa&&!vis[i>>1])
dis[v]=dis[u]+e[i].w,dfs(v,u,op);
}
void solve(int u,int size){
mx=1e9,findrt(u,0,size);
if(mx==1e9)return;
vis[rt>>1]=1;
int nw=rt,ss=size-sz[e[rt].v];
top[0]=top[1]=dis[e[rt].v]=dis[e[rt^1].v]=0;
dfs(e[rt].v,0,1),dfs(e[rt^1].v,0,0);
fp(i,1,top[0])w[s[0][i]]+=dis[s[0][i]];
fp(i,1,top[1])w[s[1][i]]+=dis[s[1][i]];
tree2::solve(e[rt].w);
fp(i,1,top[0])w[s[0][i]]-=dis[s[0][i]];
fp(i,1,top[1])w[s[1][i]]-=dis[s[1][i]];
solve(e[nw].v,sz[e[nw].v]),solve(e[nw^1].v,ss);
}
}
int main(){
// freopen("testdata.in","r",stdin);
n=read();
fp(i,2,n+n)lg[i]=lg[i>>1]+1;
int u,v;ll w;
fp(i,1,n-1)u=read(),v=read(),w=read(),tree1::qwq::add(u,v,w),tree1::qwq::add(v,u,w);
fp(i,1,n-1)u=read(),v=read(),w=read(),tree2::add(u,v,w),tree2::add(v,u,w);
fp(i,1,n-1)u=read(),v=read(),w=read(),tree3::add(u,v,w),tree3::add(v,u,w);
tree2::dfs1(1),tree2::dfs2(1,1),tree2::Pre(),
tree3::dfs1(1),tree3::dfs2(1,1);
tree1::qwq::init(),tree1::solve(1,tree1::cnt);
printf("%lld\n",res);
return 0;
}

洛谷P4220 [WC2018]通道(边分治+虚树)的更多相关文章

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

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

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

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

  3. UOJ#347. 【WC2018】通道 边分治 虚树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ347.html 题意 有三棵树,边有边权. 对于所有点对 (x,y) 求在三棵树上 x 到 y 的距离之和 ...

  4. 洛谷4103 HEOI2014大工程(虚树+dp)

    又是一道虚树好题啊 我们建出来虚树,然后考虑dp过程,我们分别令\(sum[x],mndis[x],mxdis[x],size[x]\)为子树内的路径长度和,最短链,最长链,子树内关键点个数. 对于一 ...

  5. 洛谷 P4426 - [HNOI/AHOI2018]毒瘤(虚树+dp)

    题面传送门 神仙虚树题. 首先考虑最 trival 的情况:\(m=n-1\),也就是一棵树的情况.这个我相信刚学树形 \(dp\) 的都能够秒掉罢(确信).直接设 \(dp_{i,0/1}\) 在表 ...

  6. 【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\)两点 ...

  7. 洛谷 P3377 【模板】左偏树(可并堆)

    洛谷 P3377 [模板]左偏树(可并堆) 题目描述 如题,一开始有N个小根堆,每个堆包含且仅包含一个数.接下来需要支持两种操作: 操作1: 1 x y 将第x个数和第y个数所在的小根堆合并(若第x或 ...

  8. 洛谷P3377 【模板】左偏树(可并堆) 题解

    作者:zifeiy 标签:左偏树 这篇随笔需要你在之前掌握 堆 和 二叉树 的相关知识点. 堆支持在 \(O(\log n)\) 的时间内进行插入元素.查询最值和删除最值的操作.在这里,如果最值是最小 ...

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

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

随机推荐

  1. Hadoop HA 机制学习

    一.Hadoop 系统架构 1.1 Hadoop1.x和Hadoop2.x 架构 在介绍HA之前,我们先来看下Hadoop的系统架构,这对于理解HA是至关重要的.Hadoop 1.x之前,其官方架构如 ...

  2. 手动为 Team Foundation Server 安装 SQL Server

    本主题中的步骤适用于安装 SQL Server 2012 企业版,你也可以使用安装标准版的相同步骤. 适用于 SQL 2014 的步骤与以上步骤也非常相似. 我们将在 TFS 所在的同一服务器上安装 ...

  3. C# Equals

    [C# Equals] 1.Object.Equals() The type of comparison between the current instance and the obj parame ...

  4. JAVA中集合转数组遍历

    JAVA中集合的遍历的一种方法时集合转数组遍历,也是就调用Collection中的toArray(). 代码: public static void main(String[] args) {     ...

  5. gitlab centos 安装配置运维笔记

    写在前面 如果你需要一个git服务器,为企业或自己的团队托管代码而又不希望将代码仓库存储到第三方.你可以在自己的服务器上搭建一个gitlab. 本文为我在最初安装配置gitlab服务器的时候留存的笔记 ...

  6. 视觉SLAM漫淡(二):图优化理论与g2o的使用

    视觉SLAM漫谈(二):图优化理论与g2o的使用 1    前言以及回顾 各位朋友,自从上一篇<视觉SLAM漫谈>写成以来已经有一段时间了.我收到几位热心读者的邮件.有的希望我介绍一下当前 ...

  7. Html创建表单

    echo Html::beginForm(['/site/logout'], 'post'); echo Html::submitButton(Yii::t('app', 'logout'), ['c ...

  8. java.lang.ClassNotFoundException: org.springframework.orm.hibernate3.LocalSessionFactoryBean

    Caused by: java.lang.ClassNotFoundException: org.springframework.orm.hibernate3.LocalSessionFactoryB ...

  9. HDU 3366 Passage (概率DP)

    题意:T组测试数据,一个人困在了城堡中,有n个通道,m百万money ,每个通道能直接逃出去的概率为 P[i] ,遇到士兵的概率为 q[i], 遇到士兵得给1百万money,否则会被杀掉,还有 1-p ...

  10. C# 时间戳的生成

    /**        * 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数         * @return 时间戳        */        publi ...