题解 Luogu P1099 【树网的核】
程序员文章站
2022-04-09 15:30:59
这题是真的水啊。。。 昨天模拟赛考了这题,很多人都是O($n^3$)水过,但我认为,要做就做的足够好(其实是我根本没想到O($n^3$)的做法),然后就开始想O(n)的解法。 首先看题目,前面一大堆看似是废话,其实还是有很大用处的。 问题描述中提到了树的中心,但后面却貌似没有用到,其实中心是给我们带 ......
这题是真的水啊。。。
------------
昨天模拟赛考了这题,很多人都是o($n^3$)水过,但我认为,要做就做的足够好(其实是我根本没想到o($n^3$)的做法),然后就开始想o(n)的解法。
首先看题目,前面一大堆看似是废话,其实还是有很大用处的。
问题描述中提到了树的中心,但后面却貌似没有用到,其实中心是给我们带来提示的。
既然是求最小偏心距,那必然是要在直径上找,不然偏心距并不能有过多的减少,所以第一步,定下在直径上找。
然后直径上找也要讲究方法,假如整条路径在中心的左边或右边,那么偏心距就会过大了(s太小了也没办法了)。所以,如果s足够大的话,应该将路径放在中间(指的是路径左端点到直径的左端点与路径右端点到直径右端点的距离差最小),所以先定下中心所在的边上的两个端点(如果s太小就用两端点中靠近中心的一个端点),然后哪一边的偏心距(部分的)大,就往哪边拓展,相同则同时拓展。
有两点需要注意注意:
一、如果有两条或更多直径,走到几条直径的分叉点时,就停止往这边拓展了,并且如果另一边的偏心距(部分的)小于这边,那么就不必拓展了,反正拓展了后偏心距还是这边的值。
二、只有一条直径时,要注意找所选路径上所有的偏心距,然后取大,再与直径两端点到路径两端点的距离取大(这里是指左端点与左端点,右端点与右端点的距离)
然后就愉快的ac了!(实测bzoj同样能过)
代码如下:(30ms,没进最优解qaq)
#include<cstdio>
inline int read(){
int r=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')r=(r<<1)+(r<<3)+c-'0',c=getchar();
return r*f;
}
struct e{
int v,dis,nxt;
}e[1000001];
int n,s_e,s,head[500005],dis[500005],s,t,zj,next[2][500005][2],zx[4],f[2][500005],ans=1e9;//f是部分的偏心距,next是从s(或t)出发的下一个点(直径上)
bool bj[2][500005],jg[500005];//jg就是指直径上的点,bj指的是是不是直径分叉口
inline int max(int a,int b){
return a>b?a:b;
}
inline int min(int a,int b){
return a<b?a:b;
}
inline void a_e(int u,int v,int dis){
e[++s_e]=(e){v,dis,head[u]};
head[u]=s_e;
}
void dfs(int p,int fa,int lj){
dis[p]=lj;
for(int i=head[p];i;i=e[i].nxt){
int v=e[i].v;
if(fa==v)continue;
dfs(v,p,lj+e[i].dis);
}
}
int dfs_st(int p,int fa,int sta,int end){
int b=0,d=0;
if(p==end){
jg[p]=1;
return 1;
}
for(int i=head[p];i;i=e[i].nxt){
int v=e[i].v;
if(fa==v)continue;
b=max(b,dfs_st(v,p,sta,end));
if(b==1&&!d)d=i;
if(f[sta][p]<=f[sta][v]+e[i].dis){
if(f[sta][p]==f[sta][v]+e[i].dis)bj[sta][p]=1;
else f[sta][p]=f[sta][v]+e[i].dis,bj[sta][p]=0;
}
}
if(b){
next[sta][p][0]=e[d].v;
next[sta][p][1]=e[d].dis;
jg[p]=1;
return 1;
}
return 0;
}
void find_zx(int p,int dis){
int v=next[0][p][0],val=next[0][p][1];
if(2*(val+dis)>=zj){
zx[0]=p,zx[1]=v;
if(zj-2*dis<=2*(val+dis)-zj)zx[2]=p;//靠近p
if(zj-2*dis>2*(val+dis)-zj)zx[2]=v;//靠近v
zx[3]=val;
return;
}
find_zx(v,dis+val);
}
int ansl,ansr,anss=-1e9;
void solve(int l,int r,int dis){
if(!l||!r)return;
if(ans>max(f[0][r],f[1][l]))ans=max(f[0][r],f[1][l]),ansl=l,ansr=r;//记录下目前最优左端点与右端点
if(bj[0][l]||bj[1][l]){//分叉就判断
if(next[0][r][1]+dis<=s&&f[0][r]>f[1][l])solve(l,next[0][r][0],dis+next[0][r][1]);
}
else if(bj[0][r]||bj[1][r]){
if(next[1][l][1]+dis<=s&&f[0][r]<f[1][l])solve(next[1][l][0],r,dis+next[1][l][1]);
}
else {
if(f[0][r]>f[1][l]&&next[0][r][1]+dis<=s)solve(l,next[0][r][0],dis+next[0][r][1]);
else if(f[0][r]<f[1][l]&&next[1][l][1]+dis<=s)solve(next[1][l][0],r,dis+next[1][l][1]);
else if(dis+next[0][r][1]+next[1][l][1]<=s)solve(next[1][l][0],next[0][r][0],dis+next[0][r][1]+next[1][l][1]);
}
}
int find_s(int p,int fa){
int s=0;
for(int i=head[p];i;i=e[i].nxt){
int v=e[i].v;
if(fa==v||jg[v])continue;//直径上的点不需要找,ans已经记录下最优的了
s=max(s,find_s(v,p)+e[i].dis);//每个子节点里选最大
}
return s;
}
void find_ans(int p){
anss=max(anss,find_s(p,next[1][p][0]));//路径上每个点的部分偏心距取大
if(p==ansr)return;//到达另一个端点就没什么好找的了
find_ans(next[0][p][0]);
}
int main(){
freopen("core.in","r",stdin);
freopen("core.out","w",stdout);
n=read(),s=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),dis=read();
a_e(u,v,dis);
a_e(v,u,dis);
}
dfs(1,0,0);//找直径
for(int i=1;i<=n;i++)
if(dis[s]<dis[i])s=i;
dfs(s,0,0);//找直径
for(int i=1;i<=n;i++)
if(dis[t]<dis[i])t=i;
zj=dis[t];
dfs_st(s,0,0,t);//找每个点的偏心距
dfs_st(t,0,1,s);
find_zx(s,0);//找直径中心
if(zx[3]<=s){//能选中心所在的边的两个端点就选
solve(zx[0],zx[1],zx[3]);
find_ans(ansl);//以防止有一条直径的情况
ans=max(ans,anss);
}
else ans=max(f[0][zx[2]],f[1][zx[2]]);//不能就找最挨近中心的点的偏心距
printf("%d",ans);
return 0;
}
~~完结偷偷撒花~~✿✿ヽ(°▽°)ノ✿