看到八中OJ上面吧NOIP原题的n=300扩大到了n=500000,就研究了一下O(n)的算法。
首先要确定的是如果有好几条直径的话,任意一个上面都会有一个核。 证明的话可以这么想:先考虑有两条直径的情况,因为这是一棵树,所以两个直径肯定有重合的部分,如果核在重合的部分就不用说了。如果在其他地方呢,那么是不可能的= =!为什么呢?先把两条直径简化成>--<的形状,那么重合的部分的一侧的两条链是对称的。如果核在<上,那其偏心距肯定在>--上。这么看来的话肯定不如核在中间部分优。
怎么找到一条直径呢,最简单的办法就是先任选一个点开始BFS/DFS,找到一个最远的点,这个点一定是直径的一个端点。再从这个点开始找一个最远的点就是另一个端点。这个算法的正确性很显然(参照树和直径的性质)。
我们把直径上的每一个点都作为一个根,把与其相连的非直径点作为节点构建外向树。求每一个直径上的点的到外向树的点的最大距离。
假设我们考虑链上的某个部分,那么偏心距的来源只可能是从直径的两端或者是刚才求过的外向树最大距离。
而且我们还能发现有包含关系的两部分,较长的部分的偏心距一定不会更大。
综上我们使用单调队列来维护这个最小的偏心距。整个算法的复杂度为O(n)
我本来因为懒选择了DFS,结果发现递归爆栈了。于是咬牙自己第一次写了人工栈,写的挺烂的。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | //By YY_More #include<cstdio> #include<cstring> struct edge{ int point; int data; edge *next; }; struct vertex{ edge *link; vertex(){ link=NULL; } }; struct queue{ int data; int pos; }; int max,n,s,a,b,c,dist[500010],relax[500010],F[500010],stack[500010],stack2[500010],top; edge *doing[500010]; vertex g[500010]; queue D[500010]; bool mark[500010]; void insert( int from, int to, int value){ edge *temp= new edge; (*temp).data=value; (*temp).next=g[from].link; (*temp).point=to; g[from].link=temp; } void search( int h){ mark[h]= true ; stack[++top]=h; stack2[top]=0; doing[top]=g[h].link; while (top){ if (doing[top]==NULL) { if (stack2[top]>max) max=stack2[top];top--;} else if (!mark[(*doing[top]).point]){ mark[(*doing[top]).point]= true ; stack[top+1]=(*doing[top]).point; stack2[top+1]=stack2[top]+(*doing[top]).data; doing[top+1]=g[(*doing[top]).point].link; doing[top]=(*doing[top]).next; top++; } else doing[top]=(*doing[top]).next; } } void dfs( int h){ mark[h]= true ; doing[++top]=g[h].link; stack[top]=h; while (top) if (doing[top]==NULL) top--; else if (!mark[(*doing[top]).point]){ mark[(*doing[top]).point]= true ; relax[(*doing[top]).point]=stack[top]; dist[(*doing[top]).point]=dist[stack[top]]+(*doing[top]).data; stack[top+1]=(*doing[top]).point; doing[top+1]=g[(*doing[top]).point].link; doing[top]=(*doing[top]).next; top++; } else doing[top]=(*doing[top]).next; } int main(){ scanf ( "%d%d" ,&n,&s); for ( int i=1;i<n;i++){ scanf ( "%d%d%d" ,&a,&b,&c); insert(a,b,c); insert(b,a,c); } dfs(1); int st,ed; for ( int i=1;i<=n;i++) if (dist[i]>max) {max=dist[i];ed=i;} memset (mark,0, sizeof (mark)); memset (dist,0, sizeof (dist)); dfs(ed);max=0; for ( int i=1;i<=n;i++) if (dist[i]>max) {max=dist[i];st=i;} memset (mark,0, sizeof (mark)); mark[st]= true ;F[1]=st; int num=1,now=st; do { now=relax[now]; mark[now]= true ; F[++num]=now;} while (now!=ed); int low=1,L=0,R=-1,ans=(1<<31)-1; for ( int i=1;i<=num;i++){ while (dist[F[low]]-dist[F[i]]>s) low++; max=0;search(F[i]); while (L<=R&&max>=D[R].data) R--; D[++R].pos=i; D[R].data=max; while (D[L].pos<low) L++; max=D[L].data; if (dist[F[i]]>max) max=dist[F[i]]; if (dist[st]-dist[F[low]]>max) max=dist[st]-dist[F[low]]; if (max<ans) ans=max; } printf ( "%d\n" ,ans); return 0; } |
分类:
动态规划
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)