*模擬賽要結束時,ssw02問sxk:t3 你寫的什麼? *
sxk :我終於把t3調出來了
ssw02 : 666,所以你寫的什麼?
sxk :我以前還沒寫過。
*ssw02 : ???(自閉) *
考試後 sxk : 我不給你說了我寫的是 dsu on tree 嗎,我以前還沒怎麼寫過 。
ssw02 : ???
歡迎**ssw02的部落格:
當你發現一道樹上的題,有一些統計性任務,而且不帶修改,並且只能進行單點統計之類的,複雜度還只能承受 nlogn ,那麼,先別寫某些毒瘤樹套樹,dsu on tree (樹上啟發式合併) 是乙個不錯的選擇。毒瘤noip原話 :
說實話"dsu on tree"是個極其有問題的民科叫法吧。。(沒有懟人的意思。。)
這東西幾十年前就有了啊,那個人自己yy了個鏈分治就瞎起名字。。
好的,我們切入正題,如何讓乙個 n^2 級別的暴力子樹統計變成乙個 nlogn 資料結構的演算法
我們考慮利用輕重鏈剖分的性質來對演算法複雜度進行優化。(假設你會樹剖)假設我們正在遞迴處理子樹 u 。
我們進行如下操作(亂搞):
1.我們先暴力跑 u 的輕兒子所在的子樹,同時我們刪除遞迴的貢獻值。
2.然後我們搞 u 的重兒子,這個時候我們要把貢獻給算上了。
3.我們發現子樹的答案還沒有更新,怎麼辦?再把 u 的輕兒子都遞迴一遍,同時統計累計貢獻 。
4.這時候就可以得出子樹的答案了。
5.然後,沒有然後了吧,我們好像在遞迴時就處理了每個節點吧。
實現**:
void deal( int u , int fa , int val )
}void dfs2( int u , int fa , int opt )
if( son[ u ] )dfs2( son[ u ] , u , 1 ) , s = son[ u ] ;
deal( u , fa , 1 ) ;//遞迴處理子樹,統計輕兒子貢獻
ans[ u ] = sum , s = 0 ;
if( !opt )deal( u , fa , -1 ) , sum = 0 , mx = 0 ;//memset上仙
}
同樹鏈剖分(這裡指輕重鏈剖分,不是長鏈剖分)。
注意,清除貢獻的時候,如果你用了 memset ,emmmm,恭喜您上天了 。
清除**:
ans[ u ] = sum , s = 0 ;
if( !opt )deal( u , fa , -1 ) , sum = 0 , mx = 0 ;//memset上仙
不信你看:下面是用 memset 的
題目傳送門
給一棵根為1的樹,每次詢問子樹顏色種類數
ac**:
#includeusing namespace std ;
#define ll long long
const int maxn = 100005 ;
inline int read()
int n , m , col[ maxn ] , son[ maxn ] , size[ maxn ] , cnt[ maxn ] ;
int tot = 1 , s , sum , ans[ maxn ] , head[ maxn ] , nex[ maxn*2 ] , to[ maxn*2 ] ;
void add( int x , int y )
void dfs( int u , int fa )
}void deal( int u , int fa , int val )
}void dfs2( int u , int fa , int opt )
if( son[ u ] )dfs2( son[ u ] , u , 1 ) , s = son[ u ] ;
deal( u , fa , 1 ) ;//遞迴處理子樹,統計輕兒子貢獻
ans[ u ] = sum , s = 0 ;
if( !opt )deal( u , fa , -1 ) , sum = 0 ;//memset上仙
}int main()
for( int i = 1 ; i <= n ; ++i )col[ i ] = read() ;
dfs( 1 , 1 ) ;
dfs2( 1 , 1 , 0 ) ;
m = read() ;
for( int i = 1 ; i <= m ; ++i )
return 0 ;
}
一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。
ac**:
#includeusing namespace std ;
#define ll long long
const int maxn = 100005 ;
int s=0 ; char g=getchar() ; while(g>'9'||g
while( g>='0'&&g<='9' )s=s*10+g-'0',g=getchar() ; return s ;
} int n , col[ maxn ] , son[ maxn ] , size[ maxn ] , cnt[ maxn ] ;
int tot = 1 , s , head[ maxn ] , nex[ maxn*2 ] , to[ maxn*2 ] ;
ll sum , mx , ans[ maxn ] ;
void add( int x , int y )
void dfs( int u , int fa )
}void deal( int u , int fa , int val )
}void dfs2( int u , int fa , int opt )
if( son[ u ] )dfs2( son[ u ] , u , 1 ) , s = son[ u ] ;
deal( u , fa , 1 ) ;//遞迴處理子樹,統計輕兒子貢獻
ans[ u ] = sum , s = 0 ;
if( !opt )deal( u , fa , -1 ) , sum = 0 , mx = 0 ;//memset上仙
}int main()
dfs( 1 , 1 ) ;
dfs2( 1 , 1 , 0 ) ;
for( int i = 1 ; i <= n ; ++i )printf("%lld ",ans[ i ] ) ;
return 0 ;
}
學習總結 Dsu on tree 樹上啟發式合併
rt,這只是一篇小小的總結,以便將來的回顧,並不詳細講 以前也學習過啟發式合併,大概就是像樹形dp一樣在dfs上,將兒子的資訊向父親轉移,容器是map,將兒子的資訊邊轉移邊更新答案,轉移之後便將兒子的容器清空,防止空間超限。不過對於本人而言,雖然思路較為簡便,但是因為有用到map的迭代器,所以這種寫...
啟發式合併 dsu on tree 姿勢
一場比賽有兩道,好tm巧啊 這種方法其實就是通過性質優化的暴力。遍歷時輕邊優先。首先肯定是拆位了,然後可以用trie維護一下某顆子樹內的點的串,很容易統計答案。具體要看題解了 但是暴力加 刪點會t,就要用dot了。發現計算完乙個兒子的答案之後,整顆子樹都被新增到了trie中。但如果要繼續做其他兒子所...
dsu on tree 樹上啟發式合併
詳解 dsu on tree 樹上啟發式合併 演算法總結 習題 經典例題 題意 一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。dsu on tree簡介 在o n 2 的暴力做法中,我們用cnt記錄每種顏色出現的次數,對於每個結點,遍歷這棵子樹上的所...