Solution to a problem P2081 [[NOI2012] lost amusement park]

Topic Link

This is indeed a tree \ (dp \) good (cancer) Title, and patted the idea should not be too difficult, mainly ring tree that \ (50 \) points to take the bad

Solution [NOI2012] Lost Amusement Park

Title effect: Given a \ (n-\) points \ (m \) without edges to FIG., Each side having a length, etc. Q is selected from the probability point of view, and so the probability of the current node and the neighboring node come, not a repeating node down, take the path of the desired length

Analysis: We first look \ (50 \) points, that is original is the case of a tree.

To assume \ (1 \) dot is the root, then let \ (f [u] \) from \ (U \) starting walking in this subtree, the desired length of the path to go.

Obviously we \ (1 \; min \) can be listed in a state of transition equation:

\[f[u] = \frac{\sum{(f[v] + w_{u,v})}}{siz[u]}\]

Wherein \ (V \) of \ (U \) son, \ (W_ {U, V} \) connected \ (u, v \) two path length, \ (SIZ [U] \) is \ (u \) the number of points the son

The correctness is obvious, the path length multiplied by the probability of it

Set \ (ans [u] \) of from (U \) \ point of departure, the desired length of the path to go, it is \ (U \) points to the root, a desired length of the path within the walk The tree, The final answer is \ (ans = \ frac {\ sum_ {1} ^ {n} {ans_ {i}}} {n} \)

\ (ANS [1] = f [1] \) , but how do we pass \ (ans [1] \) to seek additional \ (ANS \) ? There are many treatment techniques, such as recording a father, but if a point attached to many points you can easily take your card out, then we can use a similar \ (bfs \) method to find \ (ans \)

The tree such as:

We already know that in order to \ (1 \) is the root of the answer, that is, \ (ANS [1] \) , now suppose we ask \ (ANS [2] \) , in fact, to \ (2 \) for the root . the answer to this tree became Jiang Zi:

Paint is really a death sentence for obsessive-compulsive disorder

我们会很愉快的发现,\(3,6\)这一颗子树的\(f\)值并没有什么变动诶,\(4,5\)也是酱紫的,那么这意味这什么?我们可以在\(O(1)\)的时间内从已知的\(ans\)值推出与它相邻节点的\(ans\)值,那么只需要一次\(bfs\)就可以在\(O(n)\)的时间内算出所有的\(ans\)值啦!

设我们已知\(ans[u]\),要推出\(ans[v]\)(\(v\)\(u\)的相邻节点)

那么设少了\(v\)这个点后,\(ans[u]\)变为了\(tmp\),那么:

\(tmp = \frac{ans[u] \times degree[u] - w_{u,v} - f[v]}{degree[u] - 1}\)

其中\(degree[u]\)表示\(u\)点度数,当\(degree[u] = 1\)时特判\(tmp = 0\)(因为\(siz\)是以\(1\)为根是算的,不能直接用)

然后我们可以算\(ans[v]\)

\(ans[v] = \frac{f[v] * siz[v] + w_{u,v} + tmp}{siz[v] + 1}\),原来\(v\)一直作为子树存在,所以可以放心的用\(siz[v]\)

然后\(50 pts\) get!


然后就是毒瘤的基环树了……

我们来看看有基环时图长啥样子

好像有点歪,不管了就酱紫了

我们发现所谓基环树就是一个大的环上"生长"出了很多棵树

我们一个拓扑排序就可以把环给逮出来,基本操作

我们定义"上"这个方向是从树往基环走,"下"是从基环往树上走,每棵树的树根都在基环上

那么基环的存在对"下"的答案是没有影响的,我们只需要将"上"这部分的答案累计入答案即可

在基环上我们有两个行走防线,顺时针和逆时针,那么对于一颗树,我们可以在根新建两个虚拟点,分别表示在基环上的两个行走方向,虚拟点的\(f\)值即为在基环上路径的期望长度,当然,在程序实现中我们并不需要真的建点,只需要改改\(ans\)就好了,然后套用第一问的方法,用树根的\(ans\)去向"下"更新子树内\(ans\)即可,最后答案任为\(\frac{\sum_{1}^{n}ans_{i}}{n}\)

以顺时针走举例吧,逆时针差不多,我们设\(tot\)为从\(i\)这个点第一步向"上"走,顺时针走的期望长度:

\[tot = \sum[P_j \cdot (f[j] \cdot \frac{siz[j]}{siz[j] + 1} + w_{j-1,j})]\]\(j\)这个点向下走的期望长度为\(f[j]\),走进去的概率为\(\frac{siz[j]}{siz[j] + 1}\),注意:如果下一个点为起始点,分母为\(siz[j]\)(因为不能访问重复点,也就必须走进子树内)

对于\(P_j\),每次离开循环时我们将其乘上\(\frac{1}{siz[j] + 1}\),因为不向下走进子树,继续在环上走的概率是\(\frac{1}{siz[j] + 1}\)

虽然我们有两个虚拟点,但是我们可以将顺逆时针的答案累计起来再去更新\(ans\).那么我们现在设\(sum\)为顺逆时针的\(tot\)之和

\[ans[u] = \frac{f[u] * siz[u] + sum}{siz[u] + 2}\]

然后愉快的更新一波\(ans\)即可

上丑陋的代码:注意特判0!!

#include <cstdio>
#include <cctype>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
typedef double type;
const int maxn = 1e5 + 100;
inline int read(){
    int x = 0;char c = getchar();
    while(!isdigit(c))c = getchar();
    while(isdigit(c))x = x * 10 + c - '0',c = getchar();
    return x;
}
struct Edge{int to,dist;};
vector<Edge> G[maxn];int degree[maxn];
inline void addedge(int from,int to,int dist){
    G[from].push_back(Edge{to,dist});
    degree[to]++;
}
int n,m;
namespace Tree{//为一棵树时
    type f[maxn],ans[maxn];
    int vis[maxn],siz[maxn];
    void dfs(int u,int faz){//树形dp求f
        for(auto e : G[u]){
            if(e.to == faz)continue;
            siz[u]++;
            dfs(e.to,u);
            f[u] += f[e.to] + e.dist;
        }
        if(siz[u])f[u] /= siz[u];//特判siz是否为0
    }
    void bfs(int s){//更新答案
        queue<int> Q;
        Q.push(s),vis[s] = 1;
        while(!Q.empty()){
            int u = Q.front();Q.pop();
            for(auto e : G[u]){
                if(vis[e.to])continue;
                int nsiz = siz[u] + (u != s);//nsiz即度数,除了起始点(根节点)都要加1
                type newfu = (nsiz == 1) ? 0 : (ans[u] * nsiz - e.dist - f[e.to]) / (nsiz - 1);
                ans[e.to] = (f[e.to] * siz[e.to] + newfu + e.dist) / (siz[e.to] + 1);//这两处见上文
                Q.push(e.to),vis[e.to] = 1;
            }
        }
    }
    inline void work(){
        dfs(1,0);
        ans[1] = f[1];
        bfs(1);
        type tmp = 0;
        for(int i = 1;i <= n;i++)
            tmp += ans[i];
        printf("%.5f\n",tmp / n);
    }
}
namespace Circle{
    type f[maxn],ans[maxn];
    int vis[maxn],siz[maxn],inc[maxn],nxt[maxn],pre[maxn],tag[maxn],dis[32][32],ctot;//tag[u]表示节点u对应环上点的编号(离散化),inc表示是否在环上,dis表示距离
    void toposort(){//拓扑找环
        queue<int> Q;
        for(int i = 1;i <= n;i++)inc[i] = 1;
        for(int i = 1;i <= n;i++)
            if(degree[i] == 1)Q.push(i);
        while(!Q.empty()){
            int u = Q.front();Q.pop();
            inc[u] = 0;
            for(auto e : G[u])
                if(--degree[e.to] == 1)Q.push(e.to);
        }
    }
    void dfs(int u,int faz){//树形dp
        for(auto e : G[u]){
            if(e.to == faz || inc[e.to])continue;
            siz[u]++;
            dfs(e.to,u);
            f[u] += f[e.to] + e.dist;
        }
        if(siz[u])f[u] /= siz[u];
    }
    void bfs(int s){//同上
        queue<int> Q;
        Q.push(s),vis[s] = 1;
        while(!Q.empty()){
            int u = Q.front();Q.pop();
            for(auto e : G[u]){
                if(vis[e.to] || inc[e.to])continue;//这里注意,bfs是别跑环上去了,就在子树内更新
                int nsiz = siz[u] + (u != s);
                type newfu = (nsiz == 1) ? 0 : (ans[u] * nsiz - e.dist - f[e.to]) / (nsiz - 1);
                ans[e.to] = (f[e.to] * siz[e.to] + newfu + e.dist) / (siz[e.to] + 1);
                Q.push(e.to),vis[e.to] = 1;
            }
        }
    }
    vector<int> cir;
    void dfs_circle(int u,int faz){//找出环上对应关系
        if(tag[u])return;
        tag[u] = ++ctot;
        for(auto e : G[u]){
            if(!inc[e.to] || e.to == faz)continue;
            pre[e.to] = u,nxt[u] = e.to;
            dfs_circle(e.to,u);
            dis[tag[u]][tag[e.to]] = dis[tag[e.to]][tag[u]] = e.dist;
            break;
        }
    }
    inline void work(){
        toposort();//找环
        for(int i = 1;i <= n;i++)
            if(inc[i])dfs(i,0),cir.push_back(i);//从环上向"下"求出f
        dfs_circle(cir[0],0);
        for(int now : cir){//枚举环上点
            type div = 1.0,tot = 0,w = 0;
            for(int pos = nxt[now];pos != now;pos = nxt[pos]){
                w = dis[tag[pre[pos]]][tag[pos]];
                if(nxt[pos] == now)tot += div * (w + f[pos]);
                else tot += div * (w + f[pos] * siz[pos] / (siz[pos] + 1));
                div /= (siz[pos] + 1);
            }
            div = 1.0,w = 0;
            for(int pos = pre[now];pos != now;pos = pre[pos]){
                w = dis[tag[pos]][tag[nxt[pos]]];
                if(pre[pos] == now)tot += div * (w + f[pos]);
                else tot += div * (w + f[pos] * siz[pos] / (siz[pos] + 1));
                div /= (siz[pos] + 1);
            }
            ans[now] = (f[now] * siz[now] + tot) / (siz[now] + 2);//加入了2个虚拟点
        }//同上文题解
        for(int now : cir)siz[now] += 2;//虽然程序没有加入虚拟点,对siz的影响任然要加入,上文siz不加是因为我们要算概率
        for(int now : cir)bfs(now);//更新答案
        type sum = 0;
        for(int i = 1;i <= n;i++)sum += Circle::ans[i];
        printf("%.5f\n",sum / n);
    }
}
int main(){
    n = read();m = read();
    for(int u,v,d,i = 1;i <= m;i++)
        u = read(),v = read(),d = read(),addedge(u,v,d),addedge(v,u,d);
    if(m == n - 1)Tree::work();
    else Circle::work();
    return 0;
}

%f%lf真的迷,下次我浮点数用\(cin / cout\)\(IO\)算了,坑了我很多次了,Windows下迷得很

祝大家都可以愉快的A了这道题

Guess you like

Origin www.cnblogs.com/colazcy/p/11515080.html