2 5 4 5 1 1 5 1 2 1 1 3 5 1 5 1 4 1 1 3 1 5 1 1 2 5 1
Case #1: 2 Case #2: 3
题意:
一颗树,去掉一条边花费的费用为:去掉这条边后形成的两棵树的最大直径*这条边的权值。求去掉哪条边,花费的费用最小。
思路:
每棵树都有一个直径d,去掉边(权值为w)分两种情况。
1.去掉非直径上的边,那么花费为 d*w;
2.去掉直径上的边,那么需要求生成的两颗子树中的直径的最大值,树中有个结论就是去掉一条边后生成的两颗子树最大直径肯定是原来的树中一个端点出发的路径,那么就可以考虑用对原来树的直径的两个端点两次dfs用树形dp来解决问题了。
去掉一条边u-v后的最大直径,先算u->v,则去掉u->v后含v的子树最大直径有两种可能:
(1).从v出发的最大边+从v出发的次大边。
(2).v的子树的最大直径。
去掉v->u同理。
感想:
这题在网上找题解都基本上是不给注释的不给思路的,研究kuangbin大牛的代码研究了好久才看懂,大牛们,写博客加点注释吧,让弱菜好学习学习啦。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <string> #include <map> #include <stack> #include <vector> #include <set> #include <queue> #pragma comment (linker,"/STACK:102400000,102400000") #define maxn 100005 #define MAXN 200005 #define mod 1000000007 #define INF 0x3f3f3f3f #define pi acos(-1.0) #define eps 1e-6 typedef long long ll; using namespace std; int n,m,ans,cnt,anss; int d,st,en; int pp[maxn],vis[maxn]; int h[maxn],pre[maxn]; // 节点的深度 pre-上一个节点用于记录直径的路径 int ma[maxn],sma[maxn],mm[maxn],res[maxn]; // ma-最长边 sma-次长边 mm-最大直径 res-答案 struct Node { int v,w,id,next; }edge[MAXN]; void addedge(int u,int v,int w,int id) { cnt++; edge[cnt].v=v; edge[cnt].w=w; edge[cnt].id=id; edge[cnt].next=pp[u]; pp[u]=cnt; } void dfs1(int u,int fa) { int i,j,v; h[u]=h[fa]+1; if(d<h[u]) { st=u; d=h[u]; } for(i=pp[u];i;i=edge[i].next) { v=edge[i].v; if(v!=fa) dfs1(v,u); } } void dfs2(int u,int fa) { int i,j,v; h[u]=h[fa]+1; pre[u]=fa; if(d<h[u]) { en=u; d=h[u]; } for(i=pp[u];i;i=edge[i].next) { v=edge[i].v; if(v!=fa) dfs2(v,u); } } void dfs(int u,int fa) { int i,j,t,v; ma[u]=sma[u]=mm[u]=0; for(i=pp[u];i;i=edge[i].next) { v=edge[i].v; if(v!=fa) { dfs(v,u); if(sma[u]<ma[v]+1) { sma[u]=ma[v]+1; if(sma[u]>ma[u]) swap(sma[u],ma[u]); mm[u]=max(mm[u],sma[u]+ma[u]); // 用自己的最长边+次长边更新 } if(mm[u]<mm[v]) mm[u]=mm[v]; // 用儿子节点的最大直径更新 } } } void solve(int u,int fa) { int i,j,t,v,w,id; for(i=pp[u];i;i=edge[i].next) { v=edge[i].v; id=edge[i].id; w=edge[i].w; if(v!=fa) { if(vis[v]) res[id]=max(res[id],mm[v]*w); else res[id]=d*w; solve(v,u); } } } int main() { int i,j,t,u,v,w,x,y,test=0; scanf("%d",&t); while(t--) { scanf("%d",&n); cnt=0; memset(pp,0,sizeof(pp)); for(i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); addedge(u,v,w,i); // 建树 addedge(v,u,w,i); } d=h[0]=0; dfs1(1,0); // dfs1、dfs2找树的直径 d=0; dfs2(st,0); d--; // 直径大小 x=en; memset(vis,0,sizeof(vis)); while(x) // 标记直径路径上的点 { vis[x]=1; x=pre[x]; } memset(res,0,sizeof(res)); dfs(st,0); // 树形dp 求ma、sma 更新每个点的mm solve(st,0); // 更新res dfs(en,0); solve(en,0); ans=INF; for(i=1;i<n;i++) { if(ans>res[i]) { ans=res[i]; anss=i; } } printf("Case #%d: %d\n",++test,anss); } return 0; } /* 1 16 1 7 1 3 7 1 3 4 1 4 5 1 4 15 1 6 4 1 6 2 1 2 13 1 2 11 1 11 9 1 6 8 1 8 10 1 10 14 1 10 12 1 12 16 1 ans:7 anss:1 */