luogub P4886 快遞員(點分治)
阿新 • • 發佈:2018-12-17
記得是9月月賽題,當時做的時候覺得跟ZJOI2015幻想鄉戰略遊戲那道題很像,就寫了,然後就寫掛了。。。
我們發現假設當前點為根,我們算出\(m\)次詢問中最遠的\(a\)對點,如果這\(a\)對點,全部都兩個點在根的不同子樹中。當前點就是最優的就是答案。當全部\(a\)對點都在一個子樹中,我們把答案改為那個子樹對應的兒子,答案會變優。當有幾對點在一個子樹,另外幾對點在另外的子樹中,當前答案還是最優的。
所以本題的一個想法就是,一個一個的改變根使答案變優。
但是上述想法要求我們每一次移動一個點都要遍歷整棵樹。是在太慢了。
我們考慮用點分治的方法優化演算法。當全部\(a\)對點都在一個子樹中時,一個更優的答案在那個子樹中,我們找到這個子樹的重心當作根。這樣最多遍歷\(logn\)
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N=101000; int cnt,head[N]; int g[N],size[N],all,root,dis[N],ans1[N],ans2[N],dep[N],fa[N][25],vis[N]; int n,m,a[N],b[N],ans; struct edge{ int to,nxt,w; }e[N*2]; void add_edge(int u,int v,int w){ cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } int read(){ int sum=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();} return sum*f; } void getroot(int u,int f){ g[u]=0;size[u]=1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(vis[v]||f==v)continue; getroot(v,u); size[u]+=size[v]; g[u]=max(g[u],size[v]); } g[u]=max(g[u],all-g[u]); if(g[u]<g[root])root=u; } void getdis(int u,int f,int w){ dep[u]=dep[f]+1; fa[u][0]=f; dis[u]=w; for(int i=1;i<=20;i++)fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(v==f)continue; getdis(v,u,w+e[i].w); } } int getlca(int x,int y){ if(dep[x]<dep[y])swap(x,y); for(int i=20;i>=0;i--) if(dep[fa[x][i]]>=dep[y])x=fa[x][i]; if(x==y)return x; for(int i=20;i>=0;i--) if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i]; return fa[x][0]; } int up(int u,int x){ for(int i=20;i>=0;i--) if((x>>i)&1)u=fa[u][i],x-=(1<<i); return u; } int calc(int u){ getdis(u,0,0); int tmp=0; for(int i=1;i<=m;i++){ int x=dis[a[i]]+dis[b[i]]; if(x>tmp){ cnt=0; ans1[++cnt]=a[i];ans2[cnt]=b[i]; tmp=x; } } ans=min(ans,tmp); tmp=0; for(int i=1;i<=cnt;i++){ int x=up(ans1[i],dep[ans1[i]]-dep[u]-1); int y=up(ans2[i],dep[ans2[i]]-dep[u]-1); if(x==y){ if(tmp==0)tmp=x; else return -1; } } if(tmp==-1)return -1; return tmp; } void work(int u){ int x=calc(u); if(x==-1)return;vis[u]=1; for(int i=head[u];i;i=e[i].nxt){ int v=e[i].to; if(vis[v])continue; if(v==x){ root=0;all=size[v]; getroot(v,0); work(root); } } } int main(){ n=read();m=read(); for(int i=1;i<n;i++){ int u=read(),v=read(),w=read(); add_edge(u,v,w);add_edge(v,u,w); } for(int i=1;i<=m;i++)a[i]=read(),b[i]=read(); ans=1e9; g[0]=n+10,all=n; getroot(1,0);work(root); printf("%d",ans); return 0; }