【2021noip模拟赛day5】A. 旅游计划
【题意】
求基环树中删去一条边得到的生成树中的直径最小值
【分析】
显然,删去的一定是环上的边才能得到生成树
然后,我们把环拆成链,得到$a_1,a_2,...,a_k$
求出以$a_i$为根的树中的最大深度$dep_i$
然后考虑生成树的直径有可能有以下几种情况:
1.最大深度在某个树中,直接跑一次树的直径
2.考虑删掉$a_i$和$a_{i+1}$之间的边,此时的直径有以下可能:
①为1-i之间的路径 ②i+1到k之间的路径 ③1-i和i+1-k之间的路径
我们可以通过预处理出1-i的距离前缀和和i-k的距离后缀和来快速计算上述值
然后直径是三种情况的max,我们要所有这些max中的min即可
【代码】
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e5+5; const ll inf=1e17; int head[maxn],tot; struct edge { int to,nxt; ll v; }e[maxn<<1]; int n; void add(int x,int y,ll z) { e[++tot].to=y; e[tot].nxt=head[x]; e[tot].v=z; head[x]=tot; } int du[maxn],vis[maxn],cir[maxn],top; void get_circle(int u) { vis[u]=1; cir[++top]=u; for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; if(du[to]<=1 || vis[to]) continue; get_circle(to); } } ll dis[maxn],maxx,pt; void dfs1(int u,int fa) { for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; if(to==fa || vis[to]) continue; dis[to]=dis[u]+e[i].v; if(dis[to]>maxx) maxx=dis[to],pt=to; dfs1(to,u); } } ll dist[maxn],ans,dep[maxn]; void dfs2(int u,int fa,int root) { for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; if(to==fa || (vis[to] && to!=root)) continue; dist[to]=dist[u]+e[i].v; maxx=max(maxx,dist[to]); dfs2(to,u,root); } } ll pre_dis[maxn],suf_dis[maxn]; ll pre_ans[maxn],suf_ans[maxn]; ll pre_tmp[maxn],suf_tmp[maxn]; int main() { freopen("travel.in","r",stdin); freopen("travel.out","w",stdout); scanf("%d",&n); int x,y; ll z; for(int i=1;i<=n;i++) { scanf("%d%d%lld",&x,&y,&z); add(x,y,z); add(y,x,z); du[x]++; du[y]++; } queue <int> q; for(int i=1;i<=n;i++) if(du[i]==1) q.push(i); while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; du[to]--; if(du[to]==1) q.push(to); } } // for(int i=1;i<=n;i++) printf("%d ",du[i]); for(int i=1;i<=n;i++) if(du[i]>1) { get_circle(i); break; } for(int i=1;i<=top;i++) { int rt=cir[i]; maxx=0; dfs1(rt,0); if(maxx) dep[i]=dis[pt],maxx=0,dfs2(pt,0,rt),ans=max(ans,maxx); } /* for(int i=1;i<=top;i++) { printf("%d %lld\n",cir[i],dep[i]); }*/ cir[0]=cir[top]; for(int i=1;i<=top;i++) for(int j=head[cir[i]];j;j=e[j].nxt) { int to=e[j].to; if(to==cir[i-1]) {pre_dis[i]=pre_dis[i-1]+e[j].v; break;} } for(int i=top-1;i>=0;i--) for(int j=head[cir[i]];j;j=e[j].nxt) { int to=e[j].to; if(to==cir[i+1]) {suf_dis[i]=suf_dis[i+1]+e[j].v; break;} } // for(int i=1;i<=top;i++) // printf("%lld\n",dep[i]); maxx=-inf; for(int i=1;i<=top;i++) { if(maxx>-inf) pre_ans[i]=max(pre_ans[i-1],pre_dis[i]+dep[i]+maxx); maxx=max(maxx,dep[i]-pre_dis[i]); pre_tmp[i]=max(pre_tmp[i-1],dep[i]+pre_dis[i]); } suf_tmp[top]=suf_ans[top]=dep[top]; maxx=-inf; for(int i=top;i>=0;i--) { if(maxx>-inf) suf_ans[i]=max(suf_ans[i+1],suf_dis[i]+dep[i]+maxx); maxx=max(maxx,dep[i]-suf_dis[i]); suf_tmp[i]=max(suf_tmp[i+1],dep[i]+suf_dis[i]); } // for(int i=1;i<=top;i++) // printf("%lld %lld %lld %lld\n",pre_ans[i],pre_tmp[i],suf_ans[i],suf_tmp[i]); ll res=inf; for(int i=2;i<=top;i++) res=min(res,max(max(pre_ans[i-1],suf_ans[i]),pre_tmp[i-1]+suf_tmp[i])); printf("%lld",max(res,ans)); return 0; }
开始求直径的时候的第二次dfs,把根节点给拦住了,所以直径求错了!!!