NOIP的数据好水,一开始有好几个错结果NOIP数据就水过了??
【题目大意】
求无根树的直径上一段不超过S长的链,使得偏心距最小。具体概念见原题。
【思路】
首先明确几个性质:
(1)对于树中的任意一点,距离其最远的点一定是树的直径的某一端点。
(2)所有的直径是等价的,即任意一条所能求出的该最小偏心距相等。
于是我们可以用两次dfs求出直径。任取一个点找到离它最远的点r,再从r找到距离它最远的点l。l到r的路径就是直径。
显然在长度不超过S的情况下,链最长最好。在l到r上维护尽可能长的链,找到左右端点到直径做右端点的较大值的最小值。然后由链上各个点出发,找到不经过直径上的点抵达的其他点的最大深度。这个最大深度和之前的最小值中较大的就是答案。
为什么是整条直径上找最大深度,而不是在核上找呢?
显然,如果这个深度最深的点不是从核中的点,那么它到核的距离必定小于核的端点到直径端点的距离。所以如果有个节点到核的距离小于核的端点到直径端点的距离,那么它必定是从核上延伸出去的。
【错误点】
具体见程序。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int MAXN=500000+50; 4 const int INF=0x7fffffff; 5 struct edge 6 { 7 int to,len; 8 }; 9 vector<edge> E[MAXN]; 10 int n,s; 11 int l,r,dis[MAXN],f[MAXN],ban[MAXN]; 12 13 void addedge(int u,int v,int w) 14 { 15 E[u].push_back((edge){v,w}); 16 E[v].push_back((edge){u,w}); 17 } 18 19 void init() 20 { 21 scanf("%d%d",&n,&s); 22 for (int i=1;i<n;i++) 23 { 24 int u,v,w; 25 scanf("%d%d%d",&u,&v,&w); 26 addedge(u,v,w); 27 } 28 } 29 30 void dfs(int u,int fa) 31 { 32 f[u]=fa; 33 for (int i=0;i<E[u].size();i++) 34 { 35 int to=E[u][i].to; 36 if (ban[to] || to==f[u]) continue; 37 dis[to]=dis[u]+E[u][i].len; 38 dfs(to,u); 39 } 40 } 41 42 void getd() 43 { 44 memset(ban,0,sizeof(ban)); 45 46 l=1,r=1; 47 dfs(l,0); 48 for (int i=1;i<=n;i++) if (dis[i]>dis[r]) r=i; 49 50 l=r; 51 dis[r]=0; 52 dfs(r,0); 53 for (int i=1;i<=n;i++) if (dis[i]>dis[l]) l=i; 54 } 55 56 void solve() 57 { 58 int i=l,j=l,ans=INF; 59 for (;i;i=f[i]) 60 { 61 while (f[j] && dis[i]-dis[f[j]]<=s) j=f[j]; 62 ans=min(ans,max(dis[j],dis[l]-dis[i])); 63 //每次找到以i为一个端点的最接近于S的链 64 //比较两端点和直径端点的长度 65 } 66 for (i=l;i;i=f[i]) ban[i]=1; 67 //由于要找出不经过直径的最大深度,所以禁止访问直径上的点 68 for (int i=l;i;i=f[i]) dis[i]=0,dfs(i,f[i]); 69 //★★★★★★★注意这里i的父亲必须传进去f[i],否则就修改了直径 70 for (int i=1;i<=n;i++) ans=max(ans,dis[i]); 71 printf("%d",ans); 72 } 73 74 int main() 75 { 76 init(); 77 getd(); 78 solve(); 79 return 0; 80 }
时间: 2024-10-19 01:08:07