1. 程式人生 > >BZOJ 3244: [Noi2013]樹的計數

BZOJ 3244: [Noi2013]樹的計數

情況 info () 關系 urn pro bubuko geo 貢獻

傳送門

神仙題...

和樹的深度有關,由於 $BFS$ 序的性質,顯然可以通過把 $BFS$ 序分成若幹段來求出深度,每一段就對應某一深度從左到右的所有節點,那麽如果確定了分的段數就確定了樹的深度(分的段數 $+1$)

為了方便,先把 $BFS$ 序變成從 $1$ 到 $n$ 的序列, $DFS$ 序當然也要隨著變一下

考慮每個位置是否可以分段,無非 $3$ 種情況:

$1.$ 此位置必須分,那麽對樹的深度貢獻就是 $1$

$2.$ 此位置不能分,那麽貢獻就是 $0$

$3.$ 此位置可分可不分,那麽貢獻就是 $0.5$(此位置分的樹的方案數和不分的樹的方案數是一樣的,如果分貢獻 $1$,不分貢獻 $0$,那麽平均貢獻就是 $0.5$)

考慮用 $BFS$ 序和 $DFS$ 序之間的關系求出每個位置的限制,設節點 $i$ 的 $BFS$ 序為 $bfn[i]$,$DFS$ 序為 $dfn[i]$

對於 $BFS$ 序連續的兩點 $x,y=x+1$(此時已經按 $bfn$ 重新標號了),如果 $dfn[x]>dfn[y]$ ,說明 $y$ 在 $x$ 的下一層

大概圖長這樣:

技術分享圖片

可以發現只有這種情況才會出現 $dfn[x]>dfn[y]$ 的情況,因為如果 $x$ 和 $y$ 在同一層那麽顯然 $y$ 會更晚被 $dfs$ 到

所以如果 $dfn[x]>dfn[x+1]$,那麽 $x$ 和 $x+1$ 之間必須分層

那麽如果 $dfn[x]<dfn[x+1]$ ,是否意味著 $x$ 和 $x+1$ 之間不能分層呢,顯然是不一定的,對於這種情況我們仍然無法確定是否要分層

可能有兩種情況,$x$ 沒有兒子, $x+1$ 是 $x$ 的兄弟;$x+1$ 是 $x$ 的兒子,還是沒辦法確定,而且發現不管是哪種情況都不會對之後的序列產生影響,這保證了無法確定的位置貢獻是 $0.5$(這個位置不管切不切方案數都是一樣的)

對於 $DFS$ 序連續的兩點 $x,y$,情況比較多

如果 $x>y-1$ ($bfn[x]>bfn[y-1]$),那麽說明 $y$ 是 $x$ 某個祖先的兒子:

技術分享圖片

對限制並沒有影響

如果 $x==y-1$,那麽 $y$ 作為 $x$ 兄弟或者兒子都是合法的

技術分享圖片技術分享圖片

所以仍然無法確定貢獻,但是同樣可以發現不管是哪種情況對後面的序列都沒有影響(裏後面節點可以接到 $y$ 下面或者作為 $y$ 的下一個兄弟)

但是第三種情況有點意思:$x<y-1$

說明 $y$ 一定是 $x$ 的第一個兒子:

技術分享圖片

那麽就是說,編號從 $x$ 到 $y-1$ 的所有節點都必須要在同一層

所以對於 $x<y-1$ 的情況,區間 $[x,y)$ 的節點都不能分割

這個限制可以用一個差分數組維護

最後剩下的都不能確定了,貢獻就是 $0.5$

最後一個問題,這些限制是必要的,但是,充分嗎?

感性理解一下,很充分,如果懷疑的話打個暴力拍它幾個小時,發現沒問題,所以是充分的...

並不會證明限制充分 $qwq$

代碼很短,但是並不好想...

設 $pos[i]$ 表示 $dfn$ 為 $i$ 的節點(其實就是 $dfn$ 的反數組)

那麽對於前面最後一個限制,$x<y-1$,其實就是 $pos[i]<pos[i+1]-1$

註意一開始那些一定要分割的位置已經算過就不會再產生 $0.5$ 的貢獻了

還有第一個節點一定單獨要分一層

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<0||ch>9) { if(ch==-) f=-1; ch=getchar(); }
    while(ch>=0&&ch<=9) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e6+7;
int n;
double ans;
int dfn[N],pos[N],sum[N];//sum是差分數組
//dfn是節點的dfs序,pos是dfn的反數組,所以有 pos[dfn[i]]=i,dfn[pos[i]]=i
inline void mark(int x,int y) { sum[x]++,sum[y+1]--; }//打差分標記
int main()
{
    // freopen("1.in","r",stdin);
    n=read();
    ans=1; mark(1,1);//第一個節點一定要分一層
    for(int i=1;i<=n;i++) dfn[read()]=i;//讀入初始dfs序
    for(int i=1;i<=n;i++) pos[dfn[read()]]=i;//用初始dfs序更新變化後的pos
    //上面一行不太好想。因為bfs序重標號了,原來的節點read()就變成了i,所以pos[dfn[read()]]=i
    for(int i=1;i<=n;i++) dfn[pos[i]]=i;//用更新後的pos更新dfn
    for(int i=1;i<n;i++)//註意i不取n
    {
        if(dfn[i]>dfn[i+1]) ans++,mark(i,i);//註意這裏也要打標記,之後不會再產生0.5的貢獻
        if(pos[i]<pos[i+1]-1) mark(pos[i],pos[i+1]-1);//打標記
    }
    int now=0;
    for(int i=1;i<n;i++) now+=sum[i],ans+=(now ? 0 : 0.5);//如果這個位置不確定則貢獻為0.5
    //註意上一行i不取n,因為切的位置只有n-1個
    ans+=1;//深度等於分的段數+1
    printf("%.3lf\n%.3lf\n%.3lf",ans-0.001,ans,ans+0.001);//BZOJ上要這樣輸出...
    return 0;
}

BZOJ 3244: [Noi2013]樹的計數