1. 程式人生 > >【BZOJ1999】樹網的核,求樹的直徑+單調佇列亂搞

【BZOJ1999】樹網的核,求樹的直徑+單調佇列亂搞

傳送門
思路:
很好的一道亂搞題
原來的題目我寫的是O(n3)
由於n<=500000
所以我們可以猜一些結論來減少時間複雜度

比如說每個直徑都有最小偏心距,直徑上每個點的偏心距可以快速處理,在直徑上時[L,R]的偏心距一定小於等於[l,r]其中L<=l<=r<=R
關於直徑上每個點的偏心距,有一個很好的處理方法
以這個點為根,在不經過直徑的情況下遍歷所有能到達的點,計算出最大距離dis
那麼它的偏心距就是max(dis,到直徑左端點距離,到右端點距離)
這個結論比較顯然,但不是很好想到
然後就可以隨便找一條直徑,預處理出dis,然後一段段從前向後列舉求答案了
用單調佇列維護
複雜度O

(n)
求直徑跑兩次bfs即可,網上有詳細說明,不再贅述

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring> 
#define M 500005
using namespace std;
int n,s,tot,S,T,ans=1<<30;
int dis[M],first[M],up[M],mx[M],D[M],q[M];
bool vis[M];
int in()
{
    char ch=getchar();int t=0;
    while (ch<'0'
||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar(); return t; } struct edge{ int v,next,w; }e[M<<1]; void add(int z,int x,int y) { e[++tot]=(edge){y,first[x],z};first[x]=tot; e[++tot]=(edge){x,first[y],z};first[y]=tot; } int
bfs(int s) { queue<int>q; while (!q.empty()) q.pop(); q.push(s); up[s]=0; for (;!q.empty();q.pop()) { int x=q.front(); for (int i=first[x];i;i=e[i].next) if (!dis[e[i].v]&&e[i].v!=s) dis[e[i].v]=dis[x]+e[i].w, q.push(e[i].v), up[e[i].v]=x; } int t=1; for (int i=2;i<=n;++i) if (dis[i]>dis[t]) t=i; return t; } void bfs2(int i) { queue<int>q; while (!q.empty()) q.pop(); q.push(i); for (;!q.empty();q.pop()) { int x=q.front(); for (int j=first[x];j;j=e[j].next) if (!vis[e[j].v]&&!D[e[j].v]) D[e[j].v]=D[x]+e[j].w, q.push(e[j].v), mx[i]=max(mx[i],D[e[j].v]); } } main() { n=in();s=in(); for (int i=1;i<n;++i) add(in(),in(),in()); S=bfs(1); memset(dis,0,sizeof(dis)); T=bfs(S); for (int i=T;i;i=up[i]) vis[i]=1; for (int i=T;i;i=up[i]) bfs2(i); int L=T,R=T,head=1,tail=1; q[1]=T; for (;L;L=up[L]) { while (up[R]&&dis[L]-dis[up[R]]<=s) { R=up[R]; while (head<=tail&&mx[q[tail]]<=mx[R]) --tail; q[++tail]=R; ans=min(ans,max(mx[q[head]],max(dis[R]-dis[S],dis[T]-dis[L]))); } if (q[head]==L) ++head; } printf("%d\n",ans); }