生活随笔
收集整理的這篇文章主要介紹了
支配树学习思路/模板
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
首先解釋什么是支配樹。 首先我們先看一下DAG,我們現在有一個DAG: 如果我們從1出發,對于圖中的一個點pp p ,在從1到pp p 的不同路徑中,總有一些點是必經的,例如圖中的8號點,我們發現在從1到8的不管是那一條路徑上,5都是必須經過的。(當然1號點和8號點也可以理解為必經的)。對于每一個點,如果把該點向上最近的一個必經點 和該點連有向邊。我們就得到了這個圖的支配樹。 還是那這個DAG舉例,我們按照上述的方法連邊(紅線): 紅邊就是該DAG的支配樹。對于一個點pp p ,從其到1的樹鏈上的每一個點,都是1到pp p 的必經點。 我們仔細觀察這個圖,我們發現,對于一個點,他最近的必經點(支配樹父親)都是他的所有入點的“LCA”。例如,5號點的入點V= { 6,2,3 },其”LCA”為1。同理8號點,入點的“LCA”為5。那么我們怎么求這個所謂的“LCA”呢? 我們都知道,“LCA”的概念僅存在于樹上,對于DAG,對于一個點集沒有所謂固定的“LCA”(因為一個點可能不止一個父親(入點))。但是對于上圖,我們可以考慮,如果一個點的所有入點向上的支配樹已經建成,那么該點的最近必經點就是這些入點在當前已建支配樹上的LCA 有點繞。。。我們看例子,比如現在我們要確定5號點的最近必經點。且V= { 6,2,3 }向上的支配樹已經建成。那么此時圖是這樣的: 從圖上非常明顯的知道:V= { 6,2,3 }的在已建成的支配樹上的LCA為1。這時我們連邊<1, 5> 對于其他的點,同理。所有我們知道,我們在依次建立支配樹的邊時,必須保證該點的所有入點的支配樹都已建成 。所以,我們必須先處理每個點的入點,在處理該點。。這不就是 拓撲排序 么?我們可以按照拓撲順序處理完這個DAG,建成支配樹。下面看一個例題。 P2597 [ZJOI2012]災難 相信這個題,已經在各種大佬博客、題解中看了不止一遍了。我們如果用上述方法建成該食物網的支配樹。那么每個點的答案,就該點的支配樹子樹的大小了。 我們看代碼:首先建圖:
void add ( int x
, int y
)
{ ver
[ ++ tot
] = y
; ne
[ tot
] = he
[ x
] ; he
[ x
] = tot
; verf
[ tot
] = x
; nef
[ tot
] = hef
[ y
] ; hef
[ y
] = tot
;
}
scanf ( "%d" , & n
) ;
for ( int i
= 1 ; i
<= n
; i
++ )
{ int x
; while ( scanf ( "%d" , & x
) , x
) { di
[ i
] ++ ; add ( x
, i
) ; }
}
可以看到,在建圖的時候,我們統計入度,并同時建出反圖(一會提到為什么) 然后我們拓撲排序,得到點的拓撲序:
void topu ( )
{ for ( int i
= 1 ; i
<= n
; i
++ ) if ( ! di
[ i
] ) q
. push ( i
) ; while ( q
. size ( ) ) { int now
= q
. front ( ) ; q
. pop ( ) ; topp
[ ++ cnt
] = now
; for ( int i
= he
[ now
] ; i
; i
= ne
[ i
] ) { int y
= ver
[ i
] ; di
[ y
] -- ; if ( ! di
[ y
] ) q
. push ( y
) ; } }
}
然后我們我們按照拓撲順序建立支配樹:
void adda ( int x
, int y
)
{ vera
[ ++ tot
] = y
; nea
[ tot
] = hea
[ x
] ; hea
[ x
] = tot
;
}
for ( int i
= 1 ; i
<= n
; i
++ )
{ int x
= verf
[ hef
[ topp
[ i
] ] ] ; for ( int j
= hef
[ topp
[ i
] ] ; j
; j
= nef
[ j
] ) x
= lca ( x
, verf
[ j
] ) ; adda ( x
, topp
[ i
] ) ; d
[ topp
[ i
] ] = d
[ x
] + 1 ; f
[ topp
[ i
] ] [ 0 ] = x
; for ( int j
= 1 ; j
<= 25 ; j
++ ) f
[ topp
[ i
] ] [ j
] = f
[ f
[ topp
[ i
] ] [ j
- 1 ] ] [ j
- 1 ] ;
}
此時hea系列數組記錄的就是支配樹信息。 最后我們dfs一下支配樹,記錄子樹大小就可以了。 下面是完整ac代碼:
#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cstdlib>
#define ll long long
using namespace std
;
const int N
= 2e5 + 5 ;
int tot
, he
[ N
] , ver
[ N
] , ne
[ N
] ;
int hef
[ N
] , verf
[ N
] , nef
[ N
] ;
int tota
, hea
[ N
] , vera
[ N
] , nea
[ N
] ;
int f
[ N
] [ 30 ] , d
[ N
] ;
int di
[ N
] ;
int n
, m
, cnt
, topp
[ N
] ;
queue
< int > q
;
void add ( int x
, int y
)
{ ver
[ ++ tot
] = y
; ne
[ tot
] = he
[ x
] ; he
[ x
] = tot
; verf
[ tot
] = x
; nef
[ tot
] = hef
[ y
] ; hef
[ y
] = tot
;
}
void adda ( int x
, int y
)
{ vera
[ ++ tot
] = y
; nea
[ tot
] = hea
[ x
] ; hea
[ x
] = tot
;
}
void topu ( )
{ for ( int i
= 1 ; i
<= n
; i
++ ) if ( ! di
[ i
] ) q
. push ( i
) ; while ( q
. size ( ) ) { int now
= q
. front ( ) ; q
. pop ( ) ; topp
[ ++ cnt
] = now
; for ( int i
= he
[ now
] ; i
; i
= ne
[ i
] ) { int y
= ver
[ i
] ; di
[ y
] -- ; if ( ! di
[ y
] ) q
. push ( y
) ; } }
}
int lca ( int x
, int y
)
{ if ( d
[ x
] > d
[ y
] ) swap ( x
, y
) ; for ( int i
= 25 ; i
>= 0 ; i
-- ) { if ( d
[ f
[ y
] [ i
] ] < d
[ x
] ) continue ; y
= f
[ y
] [ i
] ; } if ( x
== y
) return x
; for ( int i
= 25 ; i
>= 0 ; i
-- ) if ( f
[ x
] [ i
] != f
[ y
] [ i
] ) x
= f
[ x
] [ i
] , y
= f
[ y
] [ i
] ; return f
[ x
] [ 0 ] ;
}
int siz
[ N
] ;
int dfs ( int now
)
{ siz
[ now
] = 1 ; for ( int i
= hea
[ now
] ; i
; i
= nea
[ i
] ) { siz
[ now
] + = dfs ( vera
[ i
] ) ; } return siz
[ now
] ;
}
int main ( )
{ scanf ( "%d" , & n
) ; for ( int i
= 1 ; i
<= n
; i
++ ) { int x
; while ( scanf ( "%d" , & x
) , x
) { di
[ i
] ++ ; add ( x
, i
) ; } } topu ( ) ; for ( int i
= 1 ; i
<= n
; i
++ ) { int x
= verf
[ hef
[ topp
[ i
] ] ] ; for ( int j
= hef
[ topp
[ i
] ] ; j
; j
= nef
[ j
] ) x
= lca ( x
, verf
[ j
] ) ; adda ( x
, topp
[ i
] ) ; d
[ topp
[ i
] ] = d
[ x
] + 1 ; f
[ topp
[ i
] ] [ 0 ] = x
; for ( int j
= 1 ; j
<= 25 ; j
++ ) f
[ topp
[ i
] ] [ j
] = f
[ f
[ topp
[ i
] ] [ j
- 1 ] ] [ j
- 1 ] ; } dfs ( 0 ) ; for ( int i
= 1 ; i
<= n
; i
++ ) printf ( "%d\n" , siz
[ i
] - 1 ) ; return 0 ;
}
很好,我們現在探討完DAG的情況了,終于進入最普遍的支配樹的學習了。即:一般有向圖的支配樹與支配點(即必經點)。 下面有一個有向圖: (藍色邊為其dfs搜索樹的邊,為方便每個點的編號都是它的dfn) 這里我引入半支配點的概念:對于圖,若存在一個簡單路徑<x,y>。如果從x到y所經歷的所有點(除了x,y)的時間戳都大于y的時間戳,我們認為x為y的半支配點 例如圖中的6號點,他的半支配點集為V = {4,7,8,1}。因為從這些點出發都可以找到一個簡單路徑到達6,且滿足上述條件(如圖紅邊所示): 對于每一個點,我們找到他的半支配點集中的dfn最小的一個,記做semi[x]semi[x] s e m i [ x ] 然后對于每一個<semi[x]semi[x] s e m i [ x ] , xx x >連邊。(如圖紅邊所示) 為什么要這么做?因為我們如果把所有非搜索樹的邊刪去,鏈接<semi[x]semi[x] s e m i [ x ] , xx x >,那么對于每個點,其支配性不變。而又因為,對于每個點xx x ,semi[x]semi[x] s e m i [ x ] 都是它搜索樹上的祖先 。 對于一個點xx x 找出所有的邊(y,x)(y,x) ( y , x ) 中對應的yy y 若dfn[y]<dfn[x]dfn[y]<dfn[x] d f n [ y ] < d f n [ x ] 且dfn[y]dfn[y] d f n [ y ] 比當前找到的semi[x]semi[x] s e m i [ x ] 的dfndfn d f n 小 那么就用semi[x]=ysemi[x]=y s e m i [ x ] = y 若dfn[y]>dfn[x]dfn[y]>dfn[x] d f n [ y ] > d f n [ x ] 找打樹上yy y 的一個祖先zz z 并且dfn[z]>dfn[x]dfn[z]>dfn[x] d f n [ z ] > d f n [ x ] 比較semi[z]semi[z] s e m i [ z ] 同semi[x]semi[x] s e m i [ x ] 決定是否用前者更新后者。 這時,我們發現圖變成了下面的樣子: 沒錯,它變成了一個DAG。結合上述定理(支配性不變)。我直接按照DAG的支配樹求法去求這個圖的支配樹。除此之外,但是我們有更好的辦法。 對于一個點的最近支配點,我們記為idom[x]idom[x] i d o m [ x ] ,我們知道,如果求出這個玩意,然后對于每個點建邊<idom[x],xidom[x],x i d o m [ x ] , x >就是支配樹了。。那么我們怎么求idom[x]idom[x] i d o m [ x ] 呢? (摘自洛谷) 我們令PP P 是從semi[x]semi[x] s e m i [ x ] 到xx x 的搜索樹上路徑點集(不包括semi[x]semi[x] s e m i [ x ] ) 而且zz z 是PP P 中semi[z]semi[z] s e m i [ z ] 最小的點。如果semi[z]=semi[x]semi[z]=semi[x] s e m i [ z ] = s e m i [ x ] 那么idom[x]=semi[x]idom[x]=semi[x] i d o m [ x ] = s e m i [ x ] 否則就是idom[x]=idom[z]idom[x]=idom[z] i d o m [ x ] = i d o m [ z ] 。 我們標注出每個點的支配點(逗號后面): 最后連邊建成支配樹: 萬事大吉了。:
這里給出幾個性質: 1、每個點的semisemi s e m i 是唯一 的。 2、一個點的semisemi s e m i 一定是其dfs樹祖先。 3、x的半支配點不一定是x的支配點。 4、semi的深度大于idom的深度 5、如果節點U,V滿足,V是U的祖先。那么:可以 得出V是idom[U]idom[U] i d o m [ U ] 的祖先,或者idom[U]idom[U] i d o m [ U ] 是idom[V]idom[V] i d o m [ V ] 的祖先。 P5180 【模板】支配樹 下面是ac代碼:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>
#include <queue>
#include <set>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <bitset>
#include <array>
#include <cctype>
#include <unordered_map>
#include <time.h> namespace OI
{ double start_time
= 0.0 ; void read_f ( int flag
= 0 ) { freopen ( "0.in" , "r" , stdin ) ; if ( ! flag
) freopen ( "0.out" , "w" , stdout ) ; } void fast_cin ( ) { std
:: ios
:: sync_with_stdio ( false ) ; std
:: cin
. tie ( ) ; } void run_time ( ) { std
:: cout
<< "\nESC in : " << ( clock ( ) - start_time
) * 1000.0 / CLOCKS_PER_SEC
<< "ms" << std
:: endl
; } void ct ( ) { start_time
= clock ( ) ; return ; }
}
using namespace OI
;
template < typename T
>
bool bacmp ( const T
& a
, const T
& b
) { return a
> b
; }
template < typename T
>
bool pecmp ( const T
& a
, const T
& b
) { return a
< b
; } #define ll long long
#define ull unsigned ll
#define _min(x, y) ((x)>(y)?(y):(x))
#define _max(x, y) ((x)>(y)?(x):(y))
#define max3(x, y, z) ( max( (x), max( (y), (z) ) ) )
#define min3(x, y, z) ( min( (x), min( (y), (z) ) ) )
#define pr make_pair
#define pb push_back
using namespace std
; const int N
= 2e5 + 5 ;
int n
, m
;
struct Node
{ int tot
= 0 ; int ver
[ N
<< 1 ] , he
[ N
] , ne
[ N
<< 1 ] ; void add ( int x
, int y
) { ver
[ ++ tot
] = y
; ne
[ tot
] = he
[ x
] ; he
[ x
] = tot
; }
} a
, b
, c
, d
;
int dfn
[ N
] , id
[ N
] , fa
[ N
] , cnt
;
void dfs ( int u
)
{ dfn
[ u
] = ++ cnt
; id
[ cnt
] = u
; for ( int i
= a
. he
[ u
] ; i
; i
= a
. ne
[ i
] ) { int y
= a
. ver
[ i
] ; if ( dfn
[ y
] ) continue ; fa
[ y
] = u
; dfs ( y
) ; }
}
int semi
[ N
] , idom
[ N
] , bel
[ N
] , val
[ N
] ;
int fi ( int x
)
{ if ( x
== bel
[ x
] ) return x
; int fs
= fi ( bel
[ x
] ) ; if ( dfn
[ semi
[ val
[ bel
[ x
] ] ] ] < dfn
[ semi
[ val
[ x
] ] ] ) val
[ x
] = val
[ bel
[ x
] ] ; return bel
[ x
] = fs
;
}
void tarjan ( )
{ for ( int s
= cnt
; s
> 1 ; s
-- ) { int u
= id
[ s
] ; for ( int i
= b
. he
[ u
] ; i
; i
= b
. ne
[ i
] ) { int y
= b
. ver
[ i
] ; fi ( y
) ; if ( dfn
[ semi
[ val
[ y
] ] ] < dfn
[ semi
[ u
] ] ) semi
[ u
] = semi
[ val
[ y
] ] ; } c
. add ( semi
[ u
] , u
) ; bel
[ u
] = fa
[ u
] ; u
= fa
[ u
] ; for ( int i
= c
. he
[ u
] ; i
; i
= c
. ne
[ i
] ) { int y
= c
. ver
[ i
] ; fi ( y
) ; if ( semi
[ val
[ y
] ] == u
) idom
[ y
] = u
; else idom
[ y
] = val
[ y
] ; } } for ( int i
= 2 ; i
<= cnt
; i
++ ) { int u
= id
[ i
] ; if ( idom
[ u
] != semi
[ u
] ) idom
[ u
] = idom
[ idom
[ u
] ] ; }
}
int ans
[ N
] ;
void dfs_ans ( int u
)
{ ans
[ u
] = 1 ; for ( int i
= d
. he
[ u
] ; i
; i
= d
. ne
[ i
] ) { int y
= d
. ver
[ i
] ; dfs_ans ( y
) ; ans
[ u
] + = ans
[ y
] ; }
}
int main ( )
{ scanf ( "%d%d" , & n
, & m
) ; for ( int i
= 1 ; i
<= m
; i
++ ) { int x
, y
; scanf ( "%d%d" , & x
, & y
) ; a
. add ( x
, y
) ; b
. add ( y
, x
) ; } for ( int i
= 1 ; i
<= n
; i
++ ) semi
[ i
] = bel
[ i
] = val
[ i
] = i
; dfs ( 1 ) ; tarjan ( ) ; for ( int i
= 2 ; i
<= n
; i
++ ) d
. add ( idom
[ i
] , i
) ; dfs_ans ( 1 ) ; for ( int i
= 1 ; i
<= n
; i
++ ) printf ( "%d " , ans
[ i
] ) ; puts ( "" ) ; return 0 ;
}
總結
以上是生活随笔 為你收集整理的支配树学习思路/模板 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。