题解:【AT ddcc2020 final D】Pars/ey
最劲爆的一集,重构 11k 代码是不是很赛博?
基环树上删边求直径,肯定分类讨论。
- 删的是环边:
- 删掉环边并不影响各个子树内的信息,所以可以是全局每棵树最大的那个树直径。
- 删掉一条环边后,环上的路径变为一定,可以是从一棵树出发,走环上一段路径,然后再到另一棵树,不难发现这一段是两棵树内的最长根链加上根在环上之间的距离。
- 删的是树边:
- 来自其它树,对于其它的树来说内部结构并不受影响:
- 所以可以是剩下完整的树中最大的树直径。
- 同删掉环边的第二种,新直径依然可以是跨越树的距离,不过要排除掉这棵树,并且对于每个环上的点有两个走的方向。
- 来自它本身,尽管这棵树内部结构有所改变,但是直径依然可能从它内部得到,删掉树边后树被分成两个部分,将其成为连根部分和离根部分:
- 离根部分内部的树直径。
- 连根部分的树直径。
- 连根部分新的最长根链加上环上一段路径加上到达的那一棵树的最长根链。
- 来自其它树,对于其它的树来说内部结构并不受影响:
接下来考虑如何维护这些东西。
对于树的部分,可以借助树形 DP 完成。较为基础的内容是距离根的深度(用于分辨边两个端点的父子关系)和属于哪棵子树。因为直径是由两条最长的根链拼合而成(或是最长的儿子的直径,这个信息上传即可),且割边可能在最长的根链上,因此要维护每个点子树内最长、次长、次次长的根链,这样可以一遍 dfs 得到每个点离根部分内部的直径和根链长度,同时维护掉全局的最长、次长树直径。连根部分的信息可以再倒推一遍,根据第一遍 dfs 的信息,可以知道节点的每个儿子转移来的是链长或是直径是否是最长或次长。连根部分新的直径可能是割掉连接边后另一个父节点的子树内的直径(这个信息依然是上传即可),父节点的另外两条新的最长的根链拼合而成。新的最长根链则是到根的距离拼合一条除去割掉该子树的其它根链。由此可以知道对于儿子子树内的直径,也需要维护最长和次长,做两遍 DP 可以得到全部信息。
对于环上的部分,需要一定的数据结构。先拓扑或者 tarjan 把环找出来,因为涉及方向问题,所以需要编号,记录两点间的边权正反做两遍。如果割的边在树内,那么在环上有两个行走方向,问题在于一个方向的最长距离不能超过环长的一半,所以对于环上每个点预处理出两个方向各自能到达多远的距离。套路性的断环成链复制一倍,接下来需要计算的是环上一段路径加上两个端点的树的最长根链,将环上的边抽象成线段,发现这个东西是具有结合律的:维护只有环边不带端点的最大值,环边加上左端点树最长根链的最大值,环边加上右端点树最长根链的最大值,环边加上两个端点树最长根链的最大值,可以使用线段树维护区间信息,合并类似于 GSS 系列,对于这个东西同时维护最大值选的端点是什么也是容易的,同时转移即可。那么对于删树边排除该树的跨越树最长距离,就可以在线段树上把这棵树对应的节点的后三个信息全部置为 -INF,又因为要保证环上距离不能超过总环长的一半,双指针扫一遍就好了。对于“连根部分新的最长根链加上环上一段路径加上到达的那一棵树的最长根链”,前者是树形 DP 预处理好的,假如说双指针在从左到右扫,后者实际上就是对于每个左端点在线段树上维护的第三个信息——环边加上右端点树最长根链的最大值,同样双指针扫一遍。
至此得到了相关的所有信息,最后在按照上述分类讨论拼合答案即可。于是做到了 \(\mathcal O(n \log n)\),瓶颈在于线段树上查询信息。代码的变量名非常清晰,应该能看懂每一部分在干什么。个人感觉难点在于断环成链后链上的编号、原基环树中点的编号、环点在线段树中的编号三者难以很好的对应。
#include<bits/stdc++.h>
#define ld long double
#define ui unsigned int
#define ull unsigned long long
#define int long long
#define eb emplace_back
#define pb pop_back
#define ins insert
#define mp make_pair
#define pii pair<int,int>
#define fi first
#define se second
#define power(x) ((x)*(x))
using namespace std;
namespace FastIO
{
template<typename T=int> inline T read()
{
T s=0,w=1; char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) s=(s*10)+(c^48),c=getchar();
return s*w;
}
template<typename T> inline void read(T &s)
{
s=0; int w=1; char c=getchar();
while(!isdigit(c)) {if(c=='-') w=-1; c=getchar();}
while(isdigit(c)) s=(s*10)+(c^48),c=getchar();
s=s*w;
}
template<typename T,typename... Args> inline void read(T &x,Args &...args)
{
read(x),read(args...);
}
template<typename T> inline void write(T x,char ch)
{
if(x<0) x=-x,putchar('-');
static char stk[25]; int top=0;
do {stk[top++]=x%10+'0',x/=10;} while(x);
while(top) putchar(stk[--top]);
if(ch!='~') putchar(ch);
return;
}
}
using namespace FastIO;
namespace MTool
{
#define TA template<typename T,typename... Args>
#define TT template<typename T>
static const int Mod=1e9+7;
TT inline void Swp(T &a,T &b) {T t=a;a=b;b=t;}
TT inline void cmax(T &a,T b) {a=max(a,b);}
TT inline void cmin(T &a,T b) {a=min(a,b);}
TA inline void cmax(T &a,T b,Args... args) {a=max({a,b,args...});}
TA inline void cmin(T &a,T b,Args... args) {a=min({a,b,args...});}
TT inline void Madd(T &a,T b) {a=a+b>=Mod?a+b-Mod:a+b;}
TT inline void Mdel(T &a,T b) {a=a-b<0?a-b+Mod:a-b;}
TT inline void Mmul(T &a,T b) {a=a*b%Mod;}
TT inline void Mmod(T &a) {a=(a%Mod+Mod)%Mod;}
TT inline T Cadd(T a,T b) {return a+b>=Mod?a+b-Mod:a+b;}
TT inline T Cdel(T a,T b) {return a-b<0?a-b+Mod:a-b;}
TT inline T Cmul(T a,T b) {return a*b%Mod;}
TT inline T Cmod(T a) {return (a%Mod+Mod)%Mod;}
TA inline void Madd(T &a,T b,Args... args) {Madd(a,Cadd(b,args...));}
TA inline void Mdel(T &a,T b,Args... args) {Mdel(a,Cadd(b,args...));}
TA inline void Mmul(T &a,T b,Args... args) {Mmul(a,Cmul(b,args...));}
TA inline T Cadd(T a,T b,Args... args) {return Cadd(Cadd(a,b),args...);}
TA inline T Cdel(T a,T b,Args... args) {return Cdel(Cdel(a,b),args...);}
TA inline T Cmul(T a,T b,Args... args) {return Cmul(Cmul(a,b),args...);}
TT inline T qpow(T a,T b) {int res=1; while(b) {if(b&1) Mmul(res,a); Mmul(a,a); b>>=1;} return res;}
TT inline T qmul(T a,T b) {int res=0; while(b) {if(b&1) Madd(res,a); Madd(a,a); b>>=1;} return res;}
TT inline T spow(T a,T b) {int res=1; while(b) {if(b&1) res=qmul(res,a); a=qmul(a,a); b>>=1;} return res;}
TT inline void exgcd(T A,T B,T &X,T &Y) {if(!B) return X=1,Y=0,void(); exgcd(B,A%B,Y,X),Y-=X*(A/B);}
TT inline T Ginv(T x) {T A=0,B=0; exgcd(x,Mod,A,B); return Cmod(A);}
#undef TT
#undef TA
}
using namespace MTool;
inline void file()
{
freopen(".in","r",stdin);
freopen(".out","w",stdout);
return;
}
bool Mbe;
namespace LgxTpre
{
static const int MAX=200010;
static const int inf=2147483647;
static const int INF=4557430888798830399;
int n,A[MAX],B[MAX],C[MAX],ans[MAX];
vector<pii> G[MAX];
#define val C[id]
namespace SolveLoop
{
int N,deg[MAX],isLoop[MAX];
int st,suf[2],nw,fr;
int LoopID[3][MAX<<1],EdgeID[2][MAX<<1],Eval[3][MAX<<1];
pii LoopRID[2][MAX<<1];
int all,pl,pr,psum,LoopCanReach[2][MAX<<1];
int LoopD[2],BanLoopDMix[2],LoopMix,MixLoopOutside[2][MAX<<1];
queue<int> q;
inline void FindLoop()
{
N=n,fill(isLoop+1,isLoop+N+1,1);
for(int i=1;i<=n;++i) if(deg[i]==1) q.emplace(i);
while(!q.empty())
{
int now=q.front(); q.pop();
isLoop[now]=0,--N;
for(auto [to,id]:G[now]) if(--deg[to]==1) q.emplace(to);
}
}
inline void GetLoopID(int tow)
{
nw=st,fr=0;
for(int i=1;i<=N;++i) for(auto [to,id]:G[nw]) if(isLoop[to]&&((fr&&to!=fr)||(!fr&&to==suf[tow])))
{
EdgeID[tow][i]=id,Eval[tow][i]=Eval[tow][i+N]=val;
LoopID[tow][i]=LoopID[tow][i+N]=to,LoopRID[tow][to]=mp(i,i+N);
fr=nw,nw=to; break;
}
}
inline void GetLoopCanReach(int tow)
{
pl=2,pr=1,psum=0;
while(pl<=N+1)
{
while(pr<(N<<1)&&(psum+Eval[tow][pr+1])*2<=all) ++pr,psum+=Eval[tow][pr];
LoopCanReach[tow][pl]=pr,psum-=Eval[tow][pl],++pl;
}
}
}
using namespace SolveLoop;
namespace SolveTree
{
int DistLoop[MAX],LoopBelong[MAX];
int DistLeaf[MAX][3],SubtreeDiameter[MAX],MaxChildDiameter[MAX][2];
int MaxDiameter[2],MaxDiameterRoot[2];
int DistLoopParent[MAX],DistParent[MAX];
void dfs1(int now,int father)
{
int MCD=0;
for(auto [to,id]:G[now]) if(to!=father&&!isLoop[to])
{
DistLoop[to]=DistLoop[now]+val,LoopBelong[to]=LoopBelong[now],dfs1(to,now),cmax(MCD,SubtreeDiameter[to]);
cmax(DistLeaf[now][2],DistLeaf[to][0]+val);
if(DistLeaf[now][2]>DistLeaf[now][1]) Swp(DistLeaf[now][2],DistLeaf[now][1]);
if(DistLeaf[now][1]>DistLeaf[now][0]) Swp(DistLeaf[now][1],DistLeaf[now][0]);
cmax(MaxChildDiameter[now][1],SubtreeDiameter[to]);
if(MaxChildDiameter[now][1]>MaxChildDiameter[now][0]) Swp(MaxChildDiameter[now][1],MaxChildDiameter[now][0]);
}
SubtreeDiameter[now]=max(DistLeaf[now][0]+DistLeaf[now][1],MCD);
}
void dfs2(int now,int father,int DLP,int DP,int MaxD)
{
DistLoopParent[now]=DLP,DistParent[now]=DP;
for(auto [to,id]:G[now]) if(to!=father&&!isLoop[to])
{
int MD=(MaxChildDiameter[now][0]==SubtreeDiameter[to])?MaxChildDiameter[now][1]:MaxChildDiameter[now][0];
if(DistLeaf[to][0]+val==DistLeaf[now][0])
dfs2(to,now,max(DLP,DistLoop[now]+DistLeaf[now][1]),max({DP,MD,DistLeaf[now][1]+DistLeaf[now][2],MaxD+DistLeaf[now][1]}),max(MaxD,DistLeaf[now][1])+val);
else if(DistLeaf[to][0]+val==DistLeaf[now][1])
dfs2(to,now,max(DLP,DistLoop[now]+DistLeaf[now][0]),max({DP,MD,DistLeaf[now][0]+DistLeaf[now][2],MaxD+DistLeaf[now][0]}),max(MaxD,DistLeaf[now][0])+val);
else
dfs2(to,now,max(DLP,DistLoop[now]+DistLeaf[now][0]),max({DP,MD,DistLeaf[now][0]+DistLeaf[now][1],MaxD+DistLeaf[now][0]}),max(MaxD,DistLeaf[now][0])+val);
}
}
}
using namespace SolveTree;
namespace SegmentTree
{
#define d DistLeaf
#define mid ((L+R)>>1)
struct SGT
{
struct lmy
{
int lp,rp,mlp,mrp,sum,lmx,rmx,mix;
lmy(int Lp=0,int Rp=0,int Mlp=0,int Mrp=0,int Sum=0,int Lmx=0,int Rmx=0,int Mix=0):lp(Lp),rp(Rp),mlp(Mlp),mrp(Mrp),sum(Sum),lmx(Lmx),rmx(Rmx),mix(Mix) {}
inline friend lmy operator + (lmy a,lmy b)
{
lmy res;
res.sum=a.sum+b.sum;
if(a.lmx<a.sum+b.lmx) res.lmx=a.sum+b.lmx,res.lp=b.lp; else res.lmx=a.lmx,res.lp=a.lp;
if(b.rmx<b.sum+a.rmx) res.rmx=b.sum+a.rmx,res.rp=a.rp; else res.rmx=b.rmx,res.rp=b.rp;
if(a.mix>=b.mix&&a.mix>=a.rmx+b.lmx) res.mix=a.mix,res.mlp=a.mlp,res.mrp=a.mrp;
else if(b.mix>=a.mix&&b.mix>=a.rmx+b.lmx) res.mix=b.mix,res.mlp=b.mlp,res.mrp=b.mrp;
else res.mix=a.rmx+b.lmx,res.mlp=b.lp,res.mrp=a.rp;
return res;
}
}info[MAX<<4];
inline void pushup(int i) {info[i]=info[i<<1]+info[i<<1|1];}
void build(int i,int L,int R,int k)
{
if(L==R) return info[i]=(lmy){LoopID[k][L],LoopID[k][L-1],LoopID[k][L],LoopID[k][L-1],Eval[k][L],Eval[k][L]+d[LoopID[k][L]][0],Eval[k][L]+d[LoopID[k][L-1]][0],Eval[k][L]+d[LoopID[k][L]][0]+d[LoopID[k][L-1]][0]},void();
build(i<<1,L,mid,k),build(i<<1|1,mid+1,R,k);
pushup(i);
}
inline void build(int k) {build(1,1,N<<1,k);}
void modify(int i,int x,int L,int R,lmy k)
{
if(L==R) return info[i]=k,void();
if(x<=mid) modify(i<<1,x,L,mid,k); else modify(i<<1|1,x,mid+1,R,k);
pushup(i);
}
inline void modify(int x,lmy k) {modify(1,x,1,N<<1,k);}
lmy query(int i,int l,int r,int L,int R)
{
if(l<=L&&R<=r) return info[i];
if(r<=mid) return query(i<<1,l,r,L,mid);
if(l>mid) return query(i<<1|1,l,r,mid+1,R);
return query(i<<1,l,r,L,mid)+query(i<<1|1,l,r,mid+1,R);
}
inline lmy query(int l,int r) {return query(1,l,r,1,N<<1);}
}T[2],BanTree;
#undef d
#undef mid
}
using namespace SegmentTree;
inline void lmy_forever()
{
read(n);
for(int i=1;i<=n;++i) read(A[i],B[i],C[i]),G[A[i]].eb(mp(B[i],i)),G[B[i]].eb(mp(A[i],i)),++deg[A[i]],++deg[B[i]];
SolveLoop::FindLoop();
for(int i=1;i<=n;++i) if(isLoop[i])
{
LoopBelong[i]=i,SolveTree::dfs1(i,0),SolveTree::dfs2(i,0,0,0,0),st=i;
if(MaxDiameter[1]<SubtreeDiameter[i]) MaxDiameterRoot[1]=i,MaxDiameter[1]=SubtreeDiameter[i];
if(MaxDiameter[0]<MaxDiameter[1]) Swp(MaxDiameter[0],MaxDiameter[1]),Swp(MaxDiameterRoot[0],MaxDiameterRoot[1]);
}
for(auto [it,_]:G[st]) if(isLoop[it]) suf[suf[0]!=0]=it;
SolveLoop::GetLoopID(0),SolveLoop::GetLoopID(1);
SegmentTree::T[0].build(0),SegmentTree::T[1].build(1);
for(int i=1;i<=N;++i) all+=Eval[0][i];
SolveLoop::GetLoopCanReach(0),SolveLoop::GetLoopCanReach(1);
for(int i=2;i<=N+1;++i)
{
MixLoopOutside[0][i-1]=MixLoopOutside[1][i-1]=-INF;
if(i<=LoopCanReach[0][i])
{
auto it0=T[0].query(i,LoopCanReach[0][i]);
if(it0.mix>LoopMix) LoopMix=it0.mix,LoopD[0]=it0.mlp,LoopD[1]=it0.mrp;
MixLoopOutside[0][i-1]=it0.lmx;
}
if(i<=LoopCanReach[1][i])
{
auto it1=T[1].query(i,LoopCanReach[1][i]);
if(it1.mix>LoopMix) LoopMix=it1.mix,LoopD[0]=it1.mlp,LoopD[1]=it1.mrp;
MixLoopOutside[1][i-1]=it1.lmx;
}
}
DistLeaf[0][0]=-INF,memcpy(LoopID[2],LoopID[0],sizeof LoopID[2]),memcpy(Eval[2],Eval[0],sizeof Eval[2]);
auto GetBanMix=[&](int k)->void
{
int resx=LoopID[2][LoopRID[0][LoopD[k]].fi]; LoopID[2][LoopRID[0][LoopD[k]].fi]=0;
int resy=LoopID[2][LoopRID[0][LoopD[k]].se]; LoopID[2][LoopRID[0][LoopD[k]].se]=0;
SegmentTree::BanTree.build(2);
for(int i=2;i<=N+1;++i) if(i<=LoopCanReach[0][i]) cmax(BanLoopDMix[k],BanTree.query(i,LoopCanReach[0][i]).mix);
LoopID[2][LoopRID[0][LoopD[k]].fi]=resx,LoopID[2][LoopRID[0][LoopD[k]].se]=resy;
};
GetBanMix(0),GetBanMix(1);
for(int i=1;i<=N;++i) cmax(ans[EdgeID[0][i]],T[0].query(1,i+1,i+N-1,1,N<<1).mix,MaxDiameter[0]);
for(int i=1;i<=n;++i)
{
int u=A[i],v=B[i],rt=LoopBelong[u];
if(isLoop[u]&&isLoop[v]) continue;
if(DistLoop[u]>DistLoop[v]) Swp(u,v);
if(rt!=MaxDiameterRoot[0]) cmax(ans[i],MaxDiameter[0]); else cmax(ans[i],MaxDiameter[1]);
if(rt!=LoopD[0]&&rt!=LoopD[1]) cmax(ans[i],LoopMix);
if(rt==LoopD[0]) cmax(ans[i],BanLoopDMix[0]);
if(rt==LoopD[1]) cmax(ans[i],BanLoopDMix[1]);
cmax(ans[i],DistLoopParent[v]+max(MixLoopOutside[0][LoopRID[0][rt].fi],MixLoopOutside[1][LoopRID[1][rt].fi]));
cmax(ans[i],SubtreeDiameter[v],DistParent[v]);
}
for(int i=1;i<=n;++i) write(ans[i],'\n');
}
}
bool Med;
signed main()
{
// file();
fprintf(stderr,"%.3lf MB\n",abs(&Med-&Mbe)/1048576.0);
int Tbe=clock();
LgxTpre::lmy_forever();
int Ted=clock();
cerr<<1e3*(Ted-Tbe)/CLOCKS_PER_SEC<<" ms\n";
return (0-0);
}