BZOJ4379 POI2015 Modernizacja autostrady
Description
給定一棵無根樹,邊權都是1,請去掉一條邊並加上一條新邊,定義直徑為最遠的兩個點的距離,請輸出所有可能的新樹的直徑的最小值和最大值。
Input
第一行包含一個正整數n(3<=n<=500000),表示這棵樹的點數。
接下來n-1行,每行包含兩個正整數u,v(1<=u,v<=n),表示u與v之間有一條邊。
Output
第一行輸出五個正整數k,x1,y1,x2,y2,其中k表示新樹直徑的最小值,x1,y1表示這種情況下要去掉的邊的兩端點,x2,y2表示這種情況下要加上的邊的兩端點。
第二行輸出五個正整數k,x1,y1,x2,y2,其中k表示新樹直徑的最大值,x1,y1表示這種情況下要去掉的邊的兩端點,x2,y2表示這種情況下要加上的邊的兩端點。
若有多組最優解,輸出任意一組。
Sample Input
6
1 2
2 3
2 4
4 5
6 5
Sample Output
3 4 2 2 5
5 2 1 1 6
Solution:
一道樹的直徑裸題。
首先要拋開列印解的部分,因為求得應該斷哪個點之後,我們可以直接在兩個連通塊內找到直徑端點或者最靠近直徑中點的點,時間複雜度是
顯然這個修改的邊一定和這棵樹的原直徑,於是先將將樹的直徑抽離出來。接下來分別討論兩種最值的情況。
對於最小值,顯然我們應該斷在直徑上。因為斷在其他邊不會改變直徑的長度,而我們的目的又是讓直徑儘可能小。接下來對於斷出的兩個連通塊,最後得到的連通塊最小直徑取決於以下三種情況:
- 在連通塊A內。
- 在連通塊B內。
- 同時經過連通塊A、B。
對於第三種情況,肯定是每個連通塊最靠近中點的點——我們定義中點指的是這棵樹直徑的半徑點位置,顯然這個中點不一定在頂點上,也可能在邊上,那麼這個最靠近中點的點有這樣一個性質:
- 樹的直徑兩端點到該點的最大值最小。
顯然我們不能
- (16/11/17更新)注意,上述這條性質也可以表述成:從任意一點出發,一定會走到某一條直徑的某個端點上,所以下面我順帶把求直徑的樸素演算法給證明了。
當然可能還有不經過原直徑的情況,我們可以進行簡要證明這是不可能的:顯然這隻可能是在抽離直徑後的某個點的子樹內才會出現。由於原樹直徑的性質,導致子樹內的最深深度一定不超過根節點到樹直徑端點的長度。假設這條直徑由
即無論如何,取原樹直徑端點一定不會比不取差。命題得證。
對於最大值,就不一定斷在直徑上了(又是一個深坑),但是可以證明的是,除了斷在直徑上,就是斷在每條鏈和直徑相連的部分。這個也可以意識流證明:
- 如果斷在下面的某條邊,那麼這棵子樹沒有獨立出來的部分就沒有卵用了,它顯然不會改變原直徑的長度。而對於剩下的子樹,顯然如果截的越高,答案可能會越大不是?所以只需要列舉斷根節點到兒子的邊即可。
此時我們可以暴力跑這個小子樹的直徑,平攤複雜度仍然為
上述內容依次按序統計即可。
在列印解的部分,如何找那個距中點最近的點?這個按照定義,從兩個直徑端點開始都查一遍到某個點的距離,最大值最小的那個(些)就是了。
所以這道題目大概把所有的樹的直徑的基本性質都來了一遍(難得自己能不看題解寫出來qvq)。
程式碼裡寫了很多類似的dfs,所以比較醜。
#include <bits/stdc++.h>
#define clear(x,val) memset(x,val,sizeof(x))
#define M 500005
using namespace std;
template <class temp>
inline void Rd(temp &res){
res=0;char c;
while(c=getchar(),c<48);
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>47);
}
template <class temp>
inline bool check_max(temp &a,temp b){
if(a>b)return false;
a=b;return true;
}
template <class temp>
inline bool check_min(temp &a,temp b){
if(a<b)return false;
a=b;return true;
}
int n,head[M];
struct edge{int v,nxt;}Edge[M<<1];
void add_edge(int u,int v,int &top){
Edge[++top]=(edge){v,head[u]};head[u]=top;
Edge[++top]=(edge){u,head[v]};head[v]=top;
}
//solve_diameter--------------
int fa[M],root=0,endroot=0;
int mx_diam,small_diam,diam[M],dtop=0,dp[M];
int sumL[M],sumR[M];
bool mark[M];
void dfs_pre(int u,int pre,int dep,int &res){
if(check_max(res,dep))root=u;
for(int j=head[u];j;j=Edge[j].nxt){
int &v=Edge[j].v;
if(v!=pre&&!mark[v])dfs_pre(v,u,dep+1,res);
}
}//直徑處理step 1
void dfs_diam(int u,int pre,int dep){
fa[u]=pre;
if(check_max(mx_diam,dep))endroot=u;
for(int j=head[u];j;j=Edge[j].nxt){
int &v=Edge[j].v;
if(v!=pre)dfs_diam(v,u,dep+1);
}
}//直徑處理step 2
int stk[M],stop=0;
void up(int u){
mark[u]=true;
while(u!=root){
stk[stop++]=u;
u=fa[u];
mark[u]=true;
}
diam[++dtop]=u;
while(stop){
++dtop,--stop;
diam[dtop]=stk[stop];
}
}//將直徑抽出
int dfs_treedp(int u,int pre){
int mx=0;
for(int j=head[u];j;j=Edge[j].nxt){
int &v=Edge[j].v;
if(v!=pre&&!mark[v])
check_max(mx,dfs_treedp(v,u)+1);
}
return mx;
}
void build_diameter(int st){
mx_diam=0,dfs_pre(st,0,0,mx_diam);
mx_diam=0,dfs_diam(root,0,0);
up(endroot);
for(int i=1;i<=dtop;i++)dp[i]=dfs_treedp(diam[i],0);
for(int i=1;i<=dtop;i++)
sumL[i]=max(sumL[i-1],i-1+dp[i]);
for(int i=dtop;i>=1;i--)
sumR[i]=max(sumR[i+1],dtop-i+dp[i]);
}
//----------------------------
int dis[M];
void dfs_once(int u,int pre,int dep,int &res){
dis[u]=dep;
if(check_max(res,dep))root=u;
for(int j=head[u];j;j=Edge[j].nxt){
int v=Edge[j].v;
if(v!=pre&&!mark[v])dfs_once(v,u,dep+1,res);
}
}
int pos;
int dfs_twice(int u,int pre,int dep,int &res){
check_max(dis[u],dep);
if(check_min(res,dis[u]))pos=u;
for(int j=head[u];j;j=Edge[j].nxt){
int v=Edge[j].v;
if(v!=pre&&!mark[v])dfs_twice(v,u,dep+1,res);
}
}
struct Ans{int val,u,v;}mx,mi;
int main(){
Rd(n);
for(int i=1,top=0,u,v;i<n;i++)
Rd(u),Rd(v),add_edge(u,v,top);
build_diameter(1);
//最小值----------------------
mi.val=n;
for(int d=1;d<dtop;d++){
int val1=sumL[d],val2=sumR[d+1];
int val=max((val1+1)/2+(val2+1)/2+1,max(val1,val2));
if(check_min(mi.val,val))mi.u=diam[d],mi.v=diam[d+1];
}
//最大值----------------------
mx.val=0;
for(int d=1;d<dtop;d++)//斷直徑的情況
if(check_max(mx.val,sumL[d]+sumR[d+1]+1))
mx.u=diam[d],mx.v=diam[d+1];
for(int d=1;d<=dtop;d++)//斷與直徑連邊的情況
for(int j=head[diam[d]];j;j=Edge[j].nxt){
int v=Edge[j].v;
if(mark[v])continue;
small_diam=0,dfs_pre(v,diam[d],0,small_diam);
small_diam=0,dfs_pre(root,0,0,small_diam);
if(check_max(mx.val,mx_diam+small_diam+1))
mx.u=diam[d],mx.v=v;
}
clear(mark,0);
//最小值----------------------
printf("%d %d %d ",mi.val,mi.u,mi.v);
mark[mi.v]=true;
small_diam=0;
dfs_pre(mi.u,mi.v,0,small_diam);
small_diam=0,dfs_once(root,0,0,small_diam);
small_diam=n,dfs_twice(root,0,0,small_diam);
printf("%d ",pos);
mark[mi.u]=true,mark[mi.v]=false;
small_diam=0;
dfs_pre(mi.v,mi.u,0,small_diam);
small_diam=0,dfs_once(root,0,0,small_diam);
small_diam=n,dfs_twice(root,0,0,small_diam);
printf("%d\n",pos);
mark[mi.u]=false;
//最大值---------------------
printf("%d %d %d ",mx.val,mx.u,mx.v);
mark[mx.u]=mark[mx.v]=true;
small_diam=0;
dfs_pre(mx.u,mx.v,0,small_diam);
printf("%d ",root);
small_diam=0;
dfs_pre(mx.v,mx.u,0,small_diam);
printf("%d\n",root);
mark[mx.u]=mark[mx.v]=false;
}