NOIP2007 树网的核
最近搞一搞树型结构……毕竟自己树的知识学的太垃圾了。
首先这道题非常明显要求树的直径。树的直径有好多好多种求法,这里我选择了一位dalao的非常简洁的dfs的方法。先看一下代码。
void dfs(int x) { for(int i = head[x];i;i = e[i].next) { if(fa[x] == e[i].to || vis[e[i].to]) continue; fa[e[i].to] = x; dis[e[i].to] = dis[x] + e[i].v; dfs(e[i].to); } }
代码非常的简短。具体的思想也很好理解,就是依次向下dfs,然后使用fa来记录在直径上都有哪些点。不过这个使用的时候要注意,就是每次要被开始进行搜索的那个点的dis赋成0.
思想就是两遍dfs。第一遍先随便搜,搜到一个与之相距最远的点(这个点必然是树的直径之一),之后再从这个点进行dfs,之后搜到一个与之距离最远的点(直径的另一个端点),这就是树的直径了。
然后怎么做?
我们可以使用直径的性质。首先来看偏心距的定义,对于一条路径F,树中与之距离最远的点到路径F的距离为F的偏心距。首先每条直径肯定会有一个最小的偏心距,所以只要求一条直径就可以啦。
之后,我们要求偏心距最小……首先我们通过直径的性质可以知道一件事,就是对于直径上一条路径的偏心距,一定是当前这条路径的两端点到直径两端点距离的最大值。这个必然成立,否则肯定存在一条比你当前求的直径还长的一条链。
那么我们就可以使用贪心的思想,每次在直径上取一条长度不大于限制条件的路径,之后每次更新最小偏心距即可。注意有一种特殊情况就是直径的总长要小于限制的长度,这时就从每个在直径上的点开始dfs,最后输出最大的值就好啦。
看一下代码。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<queue> #include<set> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 5000005; const int INF = 1000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >= '0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct node { int next,to,v; }e[M<<1]; int n,s,x,y,ecnt,head[M],dis[M],fa[M],r1,r2,ans = INF,k,z; bool vis[M]; void add(int x,int y,int z) { e[++ecnt].to = y; e[ecnt].v = z; e[ecnt].next = head[x]; head[x] = ecnt; } void dfs(int x) { for(int i = head[x];i;i = e[i].next) { if(fa[x] == e[i].to || vis[e[i].to]) continue; fa[e[i].to] = x; dis[e[i].to] = dis[x] + e[i].v; dfs(e[i].to); } } void solve() { ans = -1; for(int i = r2;i;i = fa[i]) vis[i] = 1; for(int i = r2;i;i = fa[i]) dis[i] = 0,dfs(i); rep(i,1,n) ans = max(ans,dis[i]); printf("%d\n",ans); } int main() { n = read(),s = read(); rep(i,1,n-1) x = read(),y = read(),z = read(),add(x,y,z),add(y,x,z); dfs(1); memset(fa,0,sizeof(fa)); rep(i,1,n) if(dis[i] > dis[r1]) r1 = i;//找到第一个端点 dis[r1] = 0,dfs(r1); rep(i,1,n) if(dis[i] > dis[r2]) r2 = i;//找到第二个端点 k = r2; if(dis[k] <= s) { solve();//直径长度小于限制长度的解决 return 0; } for(int i = r2;i;i = fa[i]) { while(fa[k] && dis[i] - dis[fa[k]] <= s) k = fa[k];//贪心取链 ans = min(ans,max(dis[k],dis[r2] - dis[i]));//更新最小偏心距 } printf("%d\n",ans); return 0; }
当你意识到,每个上一秒都成为永恒。