1. 程式人生 > 實用技巧 >Luogu2081 「NOI2012」 迷失遊樂園

Luogu2081 「NOI2012」 迷失遊樂園

Description

在一棵基環樹(或者樹)任選點,不能走走過的點,求走到不能走的期望長度

\(n\le 10^5\),環上的點 \(\le 20\)

Solution

樹的部分可以考慮用換根法來解決

定義 \(f_i\) 為以 \(i\) 為起點,在子樹裡面的期望路徑長度

這裡有一個小誤區:這個定義轉移的時候應該除以 \(du_i-1\) (狀態定義)

正確性用期望線性效能給

另一個部分用換根做就行

struct task1{
    int n, m, cnt=1, du[N], head[N];
    double f[N], g[N], ans, d[N];
    struct node {
        int to, dis, nxt;
    } e[N << 1];
    inline void add(int u, int v, int w) {
        e[++cnt].to = v;
        ++du[v];
        e[cnt].nxt = head[u];
        e[cnt].dis = w;
        return head[u] = cnt, void();
    }
    inline void dfs1(int x, int fa) {
    for (reg int i = head[x]; i; i = e[i].nxt) {
        int t = e[i].to;
        if (t == fa)
            continue;
        dfs1(t, x);
        d[x] += f[t] + e[i].dis;
    } f[x] = d[x] / max(1,du[x]-1);
    return;
    }
    inline void dfs2(int x, int fa) {
        for (reg int i = head[x]; i; i = e[i].nxt) {
            int t = e[i].to;
            if (t == fa)
                continue;
            d[t] += (d[x] - f[t] - e[i].dis) / max(1ll, du[x] - 1) + e[i].dis;
            dfs2(t, x);
        }
        return;
    }
    inline void main()
    {
        for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);    
        dfs1(1, 0);
        dfs2(1, 0);
        For(i, 1, n) ans += 1.0 * d[i] / du[i];
        printf("%.6lf\n", ans / n);
        return ;
    }
}T1;

然後考慮環套樹的情況

基於 \(Island\)\(Roads\ In \ The \ Kindom\) 中的套路:

把一棵基環樹看成當前子樹的根,然後分別處理掉,再對環上的點處理一下

先算出來 \(f_i\) ,然後考慮環上的點的 \(g_i\) 值,可以往左側走,也可以往右側走

可以暴力處理環上點的 \(g_i\) 值,然後再反向更新回去子樹裡面的 \(g_i\)

(我實現能力可能是真的不行,寫了一週,調了好久好久)

struct task2{
    int cnt,rt, du[N], head[N];
    double t[N<<1],f[N], g[N], ans,res[N], d[N];
    struct node {
        int to, dis, nxt;
    } e[N << 1];
    inline void add(int u, int v, int w) {
        e[++cnt].to = v;
        e[cnt].nxt = head[u];
        e[cnt].dis = w;
        return head[u] = cnt, void();
    }
    pair<int,int> h[N<<1];
    int num,vis[N];
    inline bool find(int x,int fa,int pre)
    {
        if(vis[x]==1)
        {
            h[++num]=make_pair(x,e[pre].dis); vis[x]=3;
            return 1;
        } vis[x]=1;
        for(int i=head[x];i;i=e[i].nxt)
        {
            if(i==(pre^1)) continue;
            int t=e[i].to;
            if(find(t,x,i))
            {
                if(vis[t]==3&&num!=1) return 0;
                if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis);
                return 1;   
            }
            if(num) return 0;
        }return 0;
    }
    inline void dfs1(int x,int fa)
    {
        for(reg int i=head[x];i;i=e[i].nxt)
        {
            int t=e[i].to; if(t==fa||vis[t]) continue;
            dfs1(t,x); d[x]+=f[t]+e[i].dis; du[x]++;
        }
        if(du[x]) f[x]=d[x]/du[x]; if(x!=rt) du[x]++;
        return ;
    }
    inline void dfs2(int x,int fa)
    {
        for(reg int i=head[x];i;i=e[i].nxt)
        {
            int t=e[i].to; if(t==fa||vis[t]) continue;
            double tmp=(res[x]*du[x]-e[i].dis-f[t])/max(1,du[x]-1);   
            res[t]=(f[t]*(du[t]-1)+tmp+e[i].dis)/du[t];
            dfs2(t,x); 
        } return ;
    }
    inline void main()
    {
        cnt=1; for(reg int i=1,u,v,w;i<=m;++i) u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
        find(1,0,0); 
        memset(vis,0,sizeof(vis)); For(i,1,num) vis[h[i].first]=1;
        For(i,1,num) h[i+num]=h[i],rt=h[i].first,dfs1(h[i].first,0);
        
        For(i,1,num) 
        {
            int nd=h[i].second; 
            double np=1;
            For(j,i+1,num+i-1)
            {
                if(j!=num+i-1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np;
                else g[h[i].first]+=(nd+f[h[j].first])*np;
                nd=h[j].second; np/=(du[h[j].first]+1);
            }
            nd=h[i+num-1].second; np=1;
            Down(j,i+1,num+i-1)
            {
                if(j!=i+1) g[h[i].first]+=(nd+f[h[j].first]*du[h[j].first]/(du[h[j].first]+1))*np;
                else g[h[i].first]+=(nd+f[h[j].first])*np;
                nd=h[j-1].second; np/=(du[h[j].first]+1);
            } 
        } 
        For(i,1,num) res[h[i].first]=(f[h[i].first]*du[h[i].first]+g[h[i].first])/(du[h[i].first]+2),du[h[i].first]+=2,dfs2(h[i].first,0);
        For(i,1,n) ans+=res[i];
        printf("%.6lf\n",ans/n);
        return ;
    }
}T2;

Review

\(1.\) 本題的最大收穫就是 \(g_i\) 的轉移一定要想清楚再寫,改來改去浪費時間

其實環的處理思路並不複雜,要不是說我把 \(g_i\) 想亂了,就是個水題……

\(2.\) 更新自己找環的方法:

    inline bool find(int x,int fa,int pre)
    {
        if(vis[x]==1)
        {
            h[++num]=make_pair(x,e[pre].dis); vis[x]=3;
            return 1;
        } vis[x]=1;
        for(int i=head[x];i;i=e[i].nxt)
        {
            if(i==(pre^1)) continue;
            int t=e[i].to;
            if(find(t,x,i))
            {
                if(vis[t]==3&&num!=1) return 0;
                if(vis[x]!=3) h[++num]=make_pair(x,e[pre].dis);
                return 1;   
            }
            if(num) return 0;
        }return 0;
    }

改的地方主要是判斷了個 \(vis[t]==3\) 否則在祖先那裡會錯……

\(3.\) 正確學習期望線性性,這裡主要是在暴力處理環那部分

期望長度應該是每個邊乘上概率加和,而不是路徑長度乘上概率……

(想出來環的那個可以 \(O(len^2)\) 做就真的不難……)