这个问题挺有趣,所以锲而不舍想要知道怎么做
了解之后发现也挺直观和朴素,怎么当时就想不出来呢。。
1.求最长路
2.要移动的边肯定在最长路上
3.枚举最长路上的每条边,对于每条边u->v(权值为w),移动它的策略是把u,v两个端点接在两边子树的最长路的中间位置
4.接好后的最长路不一定是 x+w+y,还有可能是两个子树的最长路,要判断下
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> using namespace std; #define N 3000 int max(int aa,int bb) {return aa>bb?aa:bb;} int min(int aa,int bb) {return aa<bb?aa:bb;} struct Edge { int v,w; bool flag; Edge() { v=w=0;flag=true; } }edge[N*2]; int head[N],adj[N*2],e,n; int path[N],dist[N],D,end_v,end_u,temp[N]; void dfs(int u,int fa) { if(D<dist[u]) D=dist[u],end_v=u; for(int i=head[u];i!=-1;i=adj[i]) if(edge[i].v!=fa&&edge[i].flag) { dist[edge[i].v]=dist[u]+edge[i].w; temp[edge[i].v]=i; dfs(edge[i].v,u); } } void find(int root,int pre_root) { memset(dist,0,sizeof(dist)); D=-1; dfs(root,pre_root); memset(dist,0,sizeof(dist)); D=-1;end_u=end_v; dfs(end_u,-1); } int subtree_max_mid(int st,int ed,int *t,int DD) { int ret=0x3fffffff; int u=ed,j; while(u!=st) { ret=min(ret,max(dist[u],DD-dist[u])); j=t[u]; u=edge[j^1].v; } if(ret>=0x3fffffff) //没有进入while,没有子树 return 0; return ret; } void insert(int u,int v,int w) { edge[e].v=v;edge[e].w=w; adj[e]=head[u];head[u]=e++; } int main () { int test;scanf("%d",&test); for(int k=1;k<=test;++k) { memset(head,-1,sizeof(head)); e=0; scanf("%d",&n); if(n==1) { printf("Case %d: 0\n",k); continue; } int u,v,w; for(int i=1;i<n;++i) { scanf("%d%d%d",&u,&v,&w); insert(u,v,w); insert(v,u,w); } find(1,-1); for(int i=0;i<=n;++i) path[i]=temp[i]; int st,ed,j; st=end_u,ed=end_v; u=ed; int ans=0x3fffffff,res,x,y,d1,d2; while(u!=st) { j=path[u]; edge[j^1].flag=edge[j].flag=false; find(u,edge[j^1].v); d1=D; x=subtree_max_mid(end_u,end_v,temp,D); find(edge[j^1].v,u); d2=D; y=subtree_max_mid(end_u,end_v,temp,D); res=max(d1,d2); ans=min(ans,max(res,x+edge[j].w+y)); u=edge[j^1].v; edge[j^1].flag=edge[j].flag=true; } printf("Case %d: %d\n",k,ans); } //system("pause"); return 0; }