$[WC2018]$通道(虚树,边分练习)
\([WC2018]\)通道(虚树,边分练习)
感受码题的快感
这段时间真的是忙忙忙忙忙,省选之前还是露个脸,免得以后没机会了。
但是我感觉我的博客真的没啥人看,虽然我挺想要有人看的,但是自己真的没啥时间写优质博客,而且最主要的是我做的题大佬们都做过。
现在这样还不如转肯竞。。。
不管怎样,\(mona\),在努力点吧,拜托了。
拜托了。
进入正题:思路讲解
这题还是很有意思的。
题目大概是给你三棵树,你需要找到一个点对使得他们在三棵树上的路径长度加起来要最大。
重新理一下思路。
像这样的题,给定三棵树,如果不能通过一些奇妙的转化来变成熟悉的东西的话,我们就要考虑像三维偏序那样一维一维处理影响。比如这题,很显然的,三棵树之间并没有关联,类似于需要考虑三个维度,那么我们就只能如上考虑。
首先因为是树上路径问题,我们先考虑树分治,首当其冲的应该是点分治,考虑过某一点的路径,那么第一棵树的路径长度考虑每个点与重心的距离,这样一来就相当于当前分治考虑的所有点都获得了一个点权,也就是原问题在第一棵树需要考虑的路径长度转化为了每个点固定的点权,这个点权在三棵树中都是适用的,因此这样就消去了一维的贡献。
考虑到第二棵树,将第一棵树里面考虑的点拿出来,比较显然的是建一棵虚树出来,这样复杂度就不会受到影响,那么我们考虑在虚树上计算第二棵树的贡献,思考一下能不能将第二棵树的影响也化为点权的形式呢?将距离公式拆开,发现是可以看成两个点的点权加上\(lca\)的点权,因为存在\(lca\)所以还是受第二棵树的形态的影响。回顾问题是要我们求带权直径,在消除了两维的情况下带权直径是可以合并的,那么我们在第二棵树上\(Dfs\),每次合并当前点子树的路径,相当于枚举了\(lca\),距离公式中剩下的部分转化为点权,这样一来合并子树在第三棵树中就可以形象的表示为,每次给定若干点集,依次合并其中的带权直径。
然而合并的时候是有限制的,在最里层考虑合并的时候,产生贡献的点首先不能是第二棵树中同一个子树内的点,其次不能是第一棵树中同一个子树的点,第二个好说,每一个点集合并上来本来就是分开的,在只在合并不同点集的时候贡献给答案就行了,而第一个,你只能对于每个子树的点集都维护一下带权直径,再在需要的时候考虑合并。
这样点分的问题就出来了,子树太多,如果直接维护的话复杂度爆炸。
这时候我们考虑边分,如果用点分的那套写法直接改边分的话,菊花图卡到爆炸,所以我们需要对树进行一些魔改,具体来说就是建一些虚点来使得树的形态更加“好看”,利用虚点,以类似线段树的方式将同属于一个点的儿子合并在一起,适当设置点权和边权能使得这些虚点不影响树的相关信息,一般来说,点权随父亲,边权放到每个儿子的新父边上。复杂度不会证,都说是\(O(nlogn)\)的。
这样一来,我们每次就只需要考虑两棵子树了,直接维护即可。
虚树的作用
(这道题中)虚树的作用为将复杂度与关键信息数绑定,简单粗暴地将有用信息提取。
一般涉及到关键点的时候可以考虑用虚树,即每次只需要考虑某些点的时候。
这个一般还是挺裸的,值得注意的是,有些虚树的形态可能会发生变化,这个随机应变就好。
至于构建虚树的过程大概是用栈模拟最右边一条链的加入过程,好好模拟一下就好了。
边分的作用
上文有提到,它的优势主要体现在每次只需要合并两棵子树。
然后实现方面有需要注意的地方。
首先,重构树的时候注意点数以及数组大小,正反边的对应关系以及边权点权的设定。
其次我认为比较好的实现方式是链式前向星存边,再利用其对于边的编号进行标记等操作。
其他的按照点分做即可。
关于码题
模块化编程是一定不能少的,如果写乱了就完蛋了。
对于我来说,多留几个换行方便代码之间的跳跃(习惯\(Ctrl+\)上下跳跃),并且多留几个调试端口,一般的码题,想的慢写的慢不算什么,想和写最多就一两个小时,但是调起来很有可能调死人,所以一定不要吝啬思考和写代码的时间,有可能一时的疏忽会造成几小时的损失。
调试端口是用来补救的,犯错是难免的事,一般先划分为若干子问题,再逐块调试。
调试时间比较久也是很容易发生的事情,一定要保持思路的清晰,自己要测试的是什么,有可能会出什么问题。这时候如果脑袋有点蒙建议洗个脸。
还有就是如果检查几遍发现代码无误,应该重新检视思路,事实上代码出问题调不出来的情况很少见,更有可能的是你细节上忽略了什么问题。
大概就这样,下面是代码。(话说\(C++11\)真的好用)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define N 110000
#define ll long long
#define RG register
#define pb push_back
#define pr pair<int,ll>
#define mkp make_pair
#define fi first
#define sc second
#define ED 19
#define ds3 Tree3::Dis
using namespace std;
inline ll read(){
RG ll x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n; ll ans;
namespace Tree3{
int dfn[N],cnt,dep[N],f[ED][N<<1],top,Log[N<<1],to[N<<1]; ll ds[N]; vector<pr> V[N];
inline int Get(int i,int j) { return dep[i]<dep[j]?i:j; }
inline int Lca(int i,int j){ i=to[i],j=to[j]; if(i>j) swap(i,j);
int len=j-i+1,Lg=Log[len]; return Get(f[Lg][i],f[Lg][j-(1<<Lg)+1]);
}
inline ll Dis(int u,int v) { int lca=Lca(u,v); return ds[u]+ds[v]-(ds[lca]<<1); }
inline void Dfs(int k,int fa){
dfn[k]=++cnt,f[0][++top]=k,dep[k]=dep[fa]+1,to[k]=top;
for(auto i:V[k]){
int en=i.fi; ll w=i.sc; if(en==fa) continue ;
ds[en]=ds[k]+w,Dfs(en,k),f[0][++top]=k;
} return ;
}
inline void Init(){
for(RG int i=1;i<n;++i){
int x=read(),y=read(); ll w=read();
V[x].pb(mkp(y,w)),V[y].pb(mkp(x,w));
} Dfs(1,0); for(RG int i=2;i<=top;++i) Log[i]=Log[i>>1]+1;
for(RG int i=1;i<ED;++i)
for(RG int j=1;j+(1<<i)-1<=top;++j)
f[i][j]=Get(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
}
namespace Tree2{
int st[N],tot,dfn[N],cnt,dep[N],f[ED][N<<1],top,Log[N<<1],to[N<<1];
ll ds1[N],ds2[N]; vector<pr> V[N];
inline int Get(int i,int j) { return dep[i]<dep[j]?i:j; }
inline int Lca(int i,int j){ i=to[i],j=to[j]; if(i>j) swap(i,j);
int len=j-i+1,Lg=Log[len]; return Get(f[Lg][i],f[Lg][j-(1<<Lg)+1]);
}
inline void Dfs(int k,int fa){
dfn[k]=++cnt,f[0][++top]=k,dep[k]=dep[fa]+1,to[k]=top;
for(auto i:V[k]){
int en=i.fi; ll w=i.sc; if(en==fa) continue ;
ds2[en]=ds2[k]+w,Dfs(en,k),f[0][++top]=k;
} return ;
}
inline void Init(){
for(RG int i=1;i<n;++i){
int x=read(),y=read(); ll w=read();
V[x].pb(mkp(y,w)),V[y].pb(mkp(x,w));
} Dfs(1,0); for(RG int i=2;i<=top;++i) Log[i]=Log[i>>1]+1;
for(RG int i=1;i<ED;++i)
for(RG int j=1;j+(1<<i)-1<=top;++j)
f[i][j]=Get(f[i-1][j],f[i-1][j+(1<<(i-1))]);
}
int Rt,tmp[N],imp[N],tt; vector<int> s[N]; ll ans; pair<int,int> Mx[N][2];
inline ll Get(int u,int v,ll now){
if(!u||!v) return -1e18;
return ds1[u]+ds1[v]+ds2[u]+ds2[v]-(now<<1)+ds3(u,v);
}
inline pr Maxp(pr x,pr y,ll now){
int A=x.fi,B=x.sc,C=y.fi,D=y.sc; ll tmp=0,Max=0;
tmp=Get(B,C,now); if(tmp>Max) Max=tmp,x=mkp(B,C);
tmp=Get(B,D,now); if(tmp>Max) Max=tmp,x=mkp(B,D);
tmp=Get(A,C,now); if(tmp>Max) Max=tmp,x=mkp(A,C);
tmp=Get(A,D,now); if(tmp>Max) Max=tmp,x=mkp(A,D);
tmp=Get(A,B,now); if(tmp>Max) Max=tmp,x=mkp(A,B);
tmp=Get(C,D,now); if(tmp>Max) Max=tmp,x=mkp(C,D); return x;
}
inline ll Maxs(pr x,pr y,ll now){
int A=x.fi,B=x.sc,C=y.fi,D=y.sc; ll tmp=0,Max=0;
tmp=Get(B,C,now); if(tmp>Max) Max=tmp;
tmp=Get(B,D,now); if(tmp>Max) Max=tmp;
tmp=Get(A,C,now); if(tmp>Max) Max=tmp;
tmp=Get(A,D,now); if(tmp>Max) Max=tmp; return Max;
}
inline void Dfs2(int k){
Mx[k][0]=Mx[k][1]=mkp(0,0); if(imp[k]) Mx[k][imp[k]-1]=mkp(k,k);
for(int en:s[k]){
Dfs2(en);
ans=max(ans,Maxs(Mx[k][0],Mx[en][1],ds2[k]));
ans=max(ans,Maxs(Mx[k][1],Mx[en][0],ds2[k]));
Mx[k][0]=Maxp(Mx[k][0],Mx[en][0],ds2[k]);
Mx[k][1]=Maxp(Mx[k][1],Mx[en][1],ds2[k]);
} return ;
}
inline void Clear(int k) { imp[k]=0; for(int i:s[k]) Clear(i); s[k].clear(); }
inline bool cmp(int x,int y) { return dfn[x]<dfn[y]; }
inline void Insert(int u){
if(!tt) { tmp[++tt]=u; return ; }
int lca=Lca(u,tmp[tt]); if(lca==tmp[tt]) { tmp[++tt]=u; return ; }
while(tt>1&&dfn[tmp[tt-1]]>=dfn[lca]) s[tmp[tt-1]].pb(tmp[tt]),--tt;
if(tmp[tt]!=lca) s[lca].pb(tmp[tt]),tmp[tt]=lca;
tmp[++tt]=u; return ;
}
inline ll Calc(){
sort(st+1,st+1+tot,cmp),ans=tt=0;
for(RG int i=1;i<=tot;++i) Insert(st[i]);
while(tt>1) s[tmp[tt-1]].pb(tmp[tt]),--tt;
Rt=tmp[tt],Dfs2(Rt),Clear(Rt),tot=0; return ans;
}
}
namespace Tree1{
vector<pr> V[N<<2],si[N<<2]; int n,cp,top,first[N<<2];
struct mona { int nxt,en; ll w; } s[N<<3];
inline void Insert(int x,int y,ll w) { s[++top]=(mona) { first[x],y,w },first[x]=top; }
inline void Dfs(int k,int fa){
for(auto i:V[k]){
int en=i.fi; if(en==fa) continue ;
si[k].pb(mkp(en,i.sc)),Dfs(en,k);
} return ;
}
inline void ReBuild(){
for(RG int i=1;i<=n;++i){
int sz=si[i].size();
if(sz<=2)
for(auto j:si[i]){
int en=j.fi; ll w=(en<=cp)*j.sc;
Insert(i,en,w),Insert(en,i,w);
}
else{
int A=++n,B=++n,t=0; Insert(i,A,0),Insert(A,i,0);
Insert(i,B,0),Insert(B,i,0);
for(auto j:si[i]) if((t++)&1) si[A].pb(j); else si[B].pb(j);
}
}
}
int vis[N<<2],siz[N<<2],Sz,Rt,Max;
inline void Getroot(int k,int fa){
siz[k]=1;
for(RG int i=first[k];i;i=s[i].nxt){
int en=s[i].en; if(en==fa||vis[i>>1]) continue ;
Getroot(en,k),siz[k]+=siz[en];
int Mx=max(siz[en],Sz-siz[en]);
if(Mx<Max) Rt=i,Max=Mx;
} return ;
}
inline void Dfs(int k,int fa,ll ds,int op){
if(k<=cp) Tree2::st[++Tree2::tot]=k,Tree2::ds1[k]=ds,Tree2::imp[k]=op;
for(RG int i=first[k];i;i=s[i].nxt){
int en=s[i].en; if(en==fa||vis[i>>1]) continue ;
Dfs(en,k,ds+s[i].w,op);
}
}
inline void Divide(int u){
if(!u) return ; int x=s[u].en,y=s[u^1].en;
if((x==8210&&y==100002)||(x==8210&&y==100002))
++x,--x;
vis[u>>1]=1,Dfs(x,0,0,1),Dfs(y,0,0,2);
ll tmp=Tree2::Calc()+s[u].w; ans=max(tmp,ans);
int A=siz[x]<siz[y]?siz[x]:Sz-siz[y],B=siz[y]<siz[x]?siz[y]:Sz-siz[x];
Max=1e9,Sz=A,Rt=0,Getroot(x,0),Divide(Rt);
Max=1e9,Sz=B,Rt=0,Getroot(y,0),Divide(Rt);
}
inline void Init(){ cp=n,top=1;
for(RG int i=1;i<n;++i){
int x=read(),y=read(); ll w=read();
V[x].pb(mkp(y,w)),V[y].pb(mkp(x,w));
}
}
inline void Begin() { Dfs(1,0),ReBuild(),Max=1e9,Sz=n,Getroot(1,0),Divide(Rt); }
}
int main(){
n=Tree1::n=read(),Tree1::Init(),Tree2::Init(),Tree3::Init();
Tree1::Begin(),printf("%lld\n",ans);
}
随机推荐
- android 文件读写工具类
将可以序列化的对象通过base64编码后进行保存 但是感觉多数情况下,不需要采用这个功能,直接保存原始的json字符串,取出来之后再进行解析即可 package com.wotlab.home.mon ...
- UIWebView 禁止检测链接弹出UIActionSheet
解决方法一: 添加以下代码禁止检测类型 webView.dataDetectorTypes = UIDataDetectorTypeNone; 解决方法二: - (void)webViewDidFin ...
- 使用eclipse导入新项目时中文出现乱码问题
有时候在github上看到别人不错的项目想要拉下来学习学习的时候,总会出现这样的情况,实在蛋疼. 一般出现这种问题,会有三个地方需要改动: 在项目上右键选择 properties 将 text fil ...
- React-Native 之 GD (六)无数据情况处理
1.还是网络问题,在网络出现问题或者无法加载数据的时候,一般我们会展示空白页,在空白页中提示 无数据 之类的提示,比较好的还会使用 指示器 的方式告诉用户网络出现问题等等. 这边我们做以下处理,当无数 ...
- qbzt day2 下午
内容提要 高精 矩阵 筛法 先是高精除法 注意细节 高精度开方:神奇的竖式 以小数点为分界线,每两个位砍一刀 87654.321-->08|76|54|.32|1 大概就是先对第一位开方,然后相 ...
- kill的各种讯号?
kill 程序 不仅仅只是k掉某个进程, 她还有很多作用和用途. 其实, 这也是linux的程序的一个特点: 一个程序(很多是 命令行的程序), 除了主要的作用外, 还有很多" 重要的, 有 ...
- DRF中的视图集的使用
1.说明:DRF框架中的视图集: 在drf开发接口中,使用GenericAPIView和视图扩展类结合起来完成接口功能是一件很常见的事情,所以,drf的作者帮我们提前把 GenericAPIView ...
- sudo: pip:找不到命令
https://blog.csdn.net/fcku_88/article/details/84191288
- LeetCode——160 Intersection of Two Linked Lists
题目 Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3 Output: ...
- 数模常用算法系列Matlab实现-----线性规划
线性规划的 Matlab 标准形式 线性规划的目标函数可以是求最大值,也可以是求最小值,约束条件的不等号可以是小于号也可以是大于号.为了避免这种形式多样性带来的不便,Matlab 中规定线性 规划的标 ...