樹的直徑新求法
講解題目
今天考了一道題目,下面的思路二是我在考場上原創,好像沒人想到這種作法,最原始的題目,考場上的題目是這樣的:算法
你如今有1 個節點,他的標號爲1,每次加入一個節點,第i 次加入的節點標號爲i+1,且每次加入的節點父親爲已經存在的節點。你須要在每次加入節點後輸出當前樹的直徑。數組
大意:給你$n$個點,最開始時只有一個點,一號節點,而後每一次把第$i$號節點和編號比他小的節點相連,鏈接一個點後,求樹的直徑。spa
樣例輸入
第一行一個整數n。接下來n-1 行,第i+1 行一個數表示標號爲i+1 的點的父親。code
6 1 2 2 1 5
樣例輸出
一行n-1 個數,第i 個數表示加入i+1 號點後樹的直徑。blog
1 2 2 3 4
思路
先說一下常規的作法,咱們看一下題目,會發現一個性質,很好的性質。在每一次加點以後,只有三種狀況產生,第一種就是幾點以後沒有什麼用,樹的直徑仍是以前那個;第二種狀況就是當前點和上一次的一個端點組成;而最後一種和第二種同樣,只是是和另外一個端點。用字母表示一下就是:設當前直徑的表示方法爲$(x1,x2)$,表示的是以$x1,x2$爲兩個端點的鏈。設新加進來的點爲$y$,則新產生的樹的直徑只有三種狀況:$(x1,y)$、$(y,x2)$、$(x1,x2)$。只須要維護一個倍增lca 和每一個點的深度就行啦。這個不難想到(對與大佬們來講,像我這樣的蒟蒻就沒想到)。get
雖然我沒有想到,可是我自創了另外一種作法。咱們設$f[i]$表示的是以$i$號節點爲根的子樹中以$i$爲端點的最長長度。這樣咱們計算答案時就很好計算了,咱們只須要把新加進來的點旋轉到整棵樹的根,並更新$f$數組的值,把新加進來的點的$f$數組的值和上一個直徑進行對比,去最大值就能夠啦。難點就是旋轉,你們還記的splay的旋轉嗎,這道題的旋轉和splay的旋轉的思路差很少,都是把兒子旋上去。可是相對而言這個更簡單,由於咱們不須要存他的兒子有誰,只須要存父親就行了,可是咱們發現旋轉的時候旋下來的點的$f$數組的值會改變,因此咱們須要從新更新。怎麼更新呢?咱們每一次都在和當前旋下來的點有一條邊相連的全部點中選取$f$值最大的,並加一賦值,但有一個細節,就是這些點中不能包括要選上去的點。每一次更新就好啦。it
時間分析
可能有人會問,這個不是$O(N^2)$的時間複雜度嗎?雖然是指望$log_2^n$層,可是數據卡一卡就能卡成鏈,退化成$N^2$的算法啦。可是不要忘記,咱們是有旋轉操做的,旋來旋去,就成了一棵相似於平衡樹的東西,由於你也不知道怎麼旋,在沒看代碼的狀況下,因此數據你作不出來,哈哈。再想想,splay的精妙之處不也是這裏嗎?由於旋轉成了平衡樹。結束,時間複雜的$O(n*log_2^n)$。是否是很好?io
代碼
#include <stdio.h> #include <algorithm> using namespace std; #define N 200001 int head[N],to[N*2],nxt[N*2]; int ans,idx,n; int f[N]; int lenth[N]; void add(int a,int b) {nxt[++idx]=head[a],head[a]=idx,to[idx]=b;} void change(int p,int from) { if(f[p]) change(f[p],p); lenth[p]=0; for(int i=head[p];i;i=nxt[i]) if(to[i]!=from) lenth[p]=max(lenth[p],lenth[to[i]]+1); f[p]=from; } int main() { scanf("%d",&n); for(int i=2;i<=n;i++) { scanf("%d",&f[i]),add(f[i],i),add(i,f[i]); change(f[i],i),lenth[i]=lenth[f[i]]+1,f[i]=0; ans=max(ans,lenth[i]); printf("%d ",ans); } }
有不懂得,能夠發評論提問,這種作法純屬原創。class