仙人掌 && 圓方樹 && 虛樹 總結

仙人掌 && 圓方樹 && 虛樹 總結

Part1 仙人掌

定義

仙人掌是知足如下兩個限制的圖:node

  • 圖徹底聯通。
  • 不存在一條邊處在兩個環中。

其中第二個限制讓仙人掌的題作起來十分舒服。api

仙人掌的基環DP

首先勾出一棵有根生成樹。
那麼樹邊上正常轉移便可。
咱們把返祖邊造成的環歸到環上深度最淺的點上,即環頂。
那麼到環頂時,單獨跑一遍關於環的\(DP\)便可。
通常寫法爲:數據結構

void dfs(RG int u,RG int From) {
    dfn[u] = low[u] = ++ oo ; fa[u] = From ; 
    for(RG int i = head[u] ; i ; i = t[i].next) {
        RG int v = t[i].to ; 
        if(!dfn[v]) dfs(v , u) , low[u] = min(low[u] , low[v]);
        else if(v != From) low[u] = min(low[u] , dfn[v]) ;
        if(low[v] > dfn[u]) 正常的樹形DP(F(v) --> F(u))。
    }
    for(RG int i = head[u] ; i ; i = t[i].next)
        if(fa[t[i].to] != u && dfn[t[i].to] > dfn[u]) 基環DP(u,v)。
}

分清楚\(low\)\(dfn\)的含義便可。
因爲記錄了\(fa_u\),基環DP中的扣環也很是容易:函數

tot = 0 ;
for(RG int x = v; x ^ u; x = fa[x]) q[++tot] = x ;
q[++tot] = u ;

例題

例一:BZOJ4316 小\(C\)的獨立集

題意:求仙人掌的最大獨立集。
題解:作一遍正常的樹形\(DP\),遇到環則把環拉出來單獨作一遍。ui

例二:BZOJ1023 SHOI2008仙人掌圖

題意:求仙人掌的直徑。
題解:
一樣的作正常樹形\(DP\),碰到環頂則把環拉出來。
問題變爲在環上選兩個點,使其權值與距離和最大。顯然單調隊列便可。spa

Part2 圓方樹

概況

圓方樹是基於仙人掌的一種特殊數據結構。
其中圓點即圖中原來的點,方點則表明一個點雙。
咱們沿用上面處理仙人掌\(DP\)的作法。
若是是生成樹上的邊,則直接相連。
不然,把環摳出來,爲這個環新建一個方點,將環上的點與此方點連邊。
板子與上面的仙人掌DP基本同樣就不放了。
那麼咱們就能夠在圓點上維護本來圖單點信息,方點上維護點雙信息了code

例題:BZOJ2125 最短路

題意:詢問\(Q\)次,每次詢問仙人掌上兩點的最短路。
題解:
考慮建出圓方樹,方點向圓點的連邊 邊權爲此點到環頂的最短距離
那麼查詢兩點\((u,v)\)時,咱們求出其\(lca\),而後討論:隊列

  • \(lca\)爲圓點,答案即爲:\(dis[u] + dis[v] - 2*dis[lca]\)
  • \(lca\)爲方點,則\(u,v\)\(lca\)爲一個點雙。
  • 此時倍增找到\(u,v\)點雙進入點\(u',v'\),再求\(Dist(u',v')\)便可。

Part3 廣義圓方樹

概況

對於任意圖,相似圓方樹,能夠創建出廣義圓方樹。
廣義圓方樹與圓方樹的差異在與,特別的,對於兩個點的聯通量,也創建一個方點。
因此廣義圓方樹上只有圓-方邊
創建的方法與普通圓方樹建法仍是有所不一樣:遊戲

void Tarjan(int u,int From) {
    low[u] = dfn[u] = ++ oo ; stk[++Top] = u ;
    for(int e = G1.head[u] ; e ; e = G1.t[e].next) {
        if(e == (From ^ 1)) continue ;
        int v = G1.t[e].to ;
        if(!dfn[v]) {
            Tarjan(v , e) ; low[u] = min(low[u] , low[v]) ;
            if(low[v] >= dfn[u]) {
                G2.add(++N , u) ; int x = 0 ;
                blg[N] = sum ; 
                do {
                    x = stk[Top] ;
                    G2.add(x , N) ; Top -- ; 
                }while(x ^ v) ; 
            }
        }
        else low[u] = min(low[u] , dfn[v]) ; 
    }return ;
}

特別要注意重邊的問題(原圖中不能有重邊或自環),一樣時刻分清\(low\)\(dfn\)的做用便可理解。get

例題

BZOJ3331

題意:給定一張圖與一些點對路徑,對於每一個點,求出必須通過此點的點對路徑的數量。
題解
把廣義圓方樹建出來,那麼一個點對路徑上必須通過的點即圓方樹上對應路徑的圓點。
直接在廣義圓方樹上差分一下就好了。

UOJ30 Tourist

題意:給定一張圖,兩種操做:修改點權 或者 詢問兩點路徑上的點權最小值。
題解
首先把廣義圓方樹建出來,考慮用方點維護\(SCC\)中的點權最小值。
可是這樣建的話,修改一個圓點時須要把與其相連的方點所有修改一遍。
這很容易被卡成\(O(n^2)\)
因此對於方點,咱們不維護對應的環頂(即廣義圓方樹中方點的父親)。
這樣修改的時候,咱們就只用修改父親。
查詢時,若是兩點的\(lca\)爲方點,額外與\(lca\)的父親(環頂)取\(min\)便可。

[APIO2018] 鐵人兩項

題意:求有多少點對\((s,c,f)\)知足存在一條u -> c -> f且路每一個點只通過一次的路徑 的個數。
題解
顯然枚舉一下\(c\),而後算它的貢獻。
考慮廣義圓方樹上從一個圓點出發的不重複路徑有哪些。
除了普通的樹上路徑外,還能夠在與此點處於同一個方點的那些點中選出兩個。
這個好像DP一下就好了?
兩遍dfs,第一遍考慮兒子的貢獻,第二遍考慮父親的貢獻。
大力討論一下,第二邊dfs的時候記得刪去當前子樹的貢獻,簡單轉移就好了。

Part3 虛樹

概況

其實與上面的圖論沒有半毛錢關係......
虛樹是指在原樹上選出一些點構出一棵新樹,並保證新樹與原樹形態、性質相同。
通常來講,對於多組詢問,若 \(\sum\) 點數 較小,
那咱們不如對於每次詢問構出虛樹,而後就能夠\(O(n)\)進行DP啦。

實現

其實比較簡單,注意父子關係的保持便可。
首先對原樹進行一遍\(dfs\)搞定全部點的 \(dfs\)\(dfn\) 與 子樹結尾\(ed\)
那麼對於每次詢問,咱們把這些點摳出來,而後:

  • 按照\(dfn\)從小到大\(sort\)
  • 把相鄰兩個點的\(lca\)加入點集中。(由於保持樹的形態須要這些點)
  • 把 根結點 加入點集中。 (爲了使最後的虛樹聯通)
  • 按照\(dfn\)再進行一遍\(sort\)
  • 維護一個\(dfs\)順序的棧,時刻保持棧頂結點爲當前入棧點的父子關係。
  • 把入棧點與棧頂鏈接(前提是棧頂存在),而後把入棧點入棧。
  • 重複以上操做直到全部點入棧,此時虛樹構建完成,虛樹的根就是原樹的根。

代碼以下:

IL bool cmp(int a , int b) {return dfn[a] < dfn[b] ; }
IL void Build(){
    dfs(1 , 0) ; //求 dfn 與 ed
    get_query_node , put it in 'p[]' .
    sort(p + 1 , p + n + 1 , cmp) ; 
    for(int i = 1; i < n; i ++) p[i + n] = LCA(p[i] , p[i + 1]) ; 
    p[2*n] = 1 ; n = n * 2 ; 
    sort(p + 1 , p + n + 1 , cmp) ; Top = 0 ;  
    for(int i = 1; i <= n; i ++) {
        while(Top && ed[stk[Top]] < dfn[p[i]]) -- Top ; 
        if(Top) G2.add(stk[Top] , p[i] , E<stk[Top],p[i]>) ; stk[++ Top] = p[i] ; 
    }return ; 
}

虛樹上的邊壓縮了原樹上的路徑信息。
特別注意,因爲咱們會在虛樹的邊上壓縮信息以供後續處理,
而咱們爲了保證虛樹聯通因此可能額外引入了原樹的根。
因此必定要考慮額外引入點對答案的影響,如有影響,則要刪去其貢獻!(高頻錯點)

例題

如下題目的通性:多組詢問,總點數 \(\leq\) \(n\)

[SDOI2011]消耗戰

題意:一棵樹,邊有代價,每次詢問要求刪去最小代價的邊集,使得關鍵點不能到達根結點。
題解:
每次詢問建出虛樹,考慮如何\(O(n)\)進行\(DP\)
貌似記錄一會兒樹內有無關鍵點,而後一路向上選擇切掉當前邊或累加兒子子樹最優解便可。

[HEOI2014]大工程

題意:
一棵樹,邊有邊權,每次詢問給出\(K\)個點,在它們之間修\(\binom{K}{2}\)條邊。
每次要求回答三個問題:(1)最長路 ; (2)最短路 ; (3)全部路徑的長度和是多少。
題解:
每次詢問建出虛樹。
最短路最長路基礎的直徑DP便可。路徑長度和考慮一下通過這條邊的點對數就好了。
特別注意最短路與最長路DP時,
起點與終點位置只能是詢問點(不能是引入點),這個每一個點附初值時特殊處理一下就好了。

[SDOI2018]戰略遊戲

題意:一張圖,每次詢問給定點集,問有多少個點知足刪去後使得點集中任意兩點不聯通。
題解:
看到圖上連通性問題直接上圓方樹(那是什麼?上面就有......)
把圓方樹建出來,而後再建圓方樹的虛樹。
那麼答案就是"虛圓方樹"上除了詢問關鍵點外的圓點個數,直接建樹是統計便可。

[HNOI2014]世界樹

題意:
定義原樹上的一個點被其離的最近的關鍵點管轄。若距離相同則選擇編號小的。
對於每次詢問,給出一些關鍵點,試輸出每一個關鍵點管轄的點的數目。
題解:
首先把關鍵點建出虛樹。
那麼咱們是能夠O(n)DP出這些點的歸屬是吧。
這是經典的DP了,兩遍dfs,第一遍DP齣兒子最優值,第二遍再考慮父親。
而後考慮哪些沒有在虛樹中出現的點(都壓縮在邊上)。
對於虛樹上的邊,咱們分狀況討論:
若邊的兩端被同一個點管轄,那麼顯然這條邊上全部的點都歸那個點管轄。
若是兩端的點被不一樣的點管轄,那麼必定存在一個分界線。
咱們能夠倍增二分找到那個分界線,而後給兩個關鍵點加上對應貢獻便可。

CF809E Surprise me!

題意:
給定一棵 \(n\) 個節點的樹,個點有一個權值 \(a[i]\) ,保證 \(a[i]\) 是一個 \(1..n\) 的排列。

\[\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n\varphi(a_i)·\varphi(a_j)·dist(i,j)\]

其中, \(\varphi(x)\) 是歐拉函數, \(dist(i,j)\) 表示 \(i,j\) 兩個節點在樹上的距離。
題解:
首先歐拉函數有個公式:\(\varphi(a*b) = \varphi(a)\varphi(b)\frac{d}{\varphi(d)}\),其中\(d=gcd(a,b)\)
因此式子化一化?
\[Ans = \frac{1}{n(n-1)} \sum_{d=1}^n \frac{d}{\varphi(d)} \sum_{i=1}^n \sum_{j=1}^n\ [gcd(a_i,a_j)=d]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
考慮後面這一坨東西:
\[f(d) = \sum_{i=1}^n \sum_{j=1}^n [gcd(a_i,a_j)=d]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
一種莫比烏斯反演既視感啊.....定義:
\[F(d) = \sum_{d|i} f(i) = \sum_{i=1}^n \sum_{j=1}^n [d|gcd(a_i,a_j)]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
那麼\(f(d) = \sum_{d|i} \mu(\frac{i}{d})F(i)\)。怎麼求\(F(d)\)
貌似若是能\(O(n)\)求那麼總複雜度就是調和級數啊?
因此枚舉\(d\),而後把知足\(a_i = kd\)的點拿出來進行虛樹DP。
\[E(d) = \sum_{i=1}^n \sum_{j=1}^n\varphi(a_i)\varphi(a_j)(dis_i + dis_j - dis_{LCA(i,j)})\]
維護一會兒樹內的\(\sum \varphi(u)\) 與子樹內的\(\sum dep_u\varphi(u)\),而後在\(LCA\)處統計答案便可。

後記

終於寫完啦(QwQ)。 寫了這麼多,就是爲了證實學了這些毒瘤玩意後我還活着......

相關文章
相關標籤/搜索