[JZOJ5519] Hello my friend

Description

给出一棵N个节点的树,每个点有可能是黑白两种颜色的一种

现在从1号点开始随机游走(即走这个点的每条出边的概率是相同的),每到一个点,如果这个点是黑点,或者这是白点并且这个点第一次经过,那么答案+1。当走到度数为1的节点时游走停止(保证1号节点的度数大于1)

求答案的期望(对998244353取模)
N<=100000

Solution

不妨先考虑只有黑点怎么做

Fi 表示i号点到结束的期望答案

明显F[叶子]=1

可以从下到上树形DP
列出F的式子
令j为i的儿子, di 为i的度数(包括父亲),x为i的父亲
Fi=1+Fx+Fjdi

不妨假设儿子的已经求出来了,是一个常数,那么每个点的F可以表示成 Fi=kiFx+bi
Fi=1+Fx+kjFi+bjdi
化简得
(dikj)Fi=di+Fx+bj
Fi=Fxdikj+di+bjdikj

这就表示成了我们想要的形式

容易得出k[叶子]=0,b[叶子]=1
那么直接树形DP上去即可
答案就是 b1 (因为 k1=0

考虑只有白点怎么做

hi 表示i能走到x的概率(x是i的父亲)

那么 hi=1+hjhidi
化简得 hi=1dihj

从下到上树形DP

gi 表示x能走到i的概率,p为x的儿子
gi=1+gxgi+pihpgidx

化简得 gi=1dxpihpgx

从上到下树形DP
那么每个白点的贡献就是1走到这个点的概率
g一路乘下来就是了

考虑黑白点都有

结合黑点的算法和白点的算法,白点单独计算,计算黑点的时候如果走到白点就把那个1去掉就行了

Code

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define mo 998244353
#define N 100005
#define LL long long
using namespace std;
int n,m,nt[2*N],fs[N],dt[2*N],c[N],rd[N];
LL f[N],b[N],g[N],h[N],ans,hs[N];
void link(int x,int y)
{
    nt[++m]=fs[x];
    dt[fs[x]=m]=y;
}
LL ksm(LL k,LL n)
{
    LL s=1;
    for(;n;k=k*k%mo,n>>=1) if(n&1) s=s*k%mo;
    return s;
}
void dp(int k,int fa)
{
    if(nt[fs[k]]==0)
    {
        f[k]=0,b[k]=c[k];
        h[k]=0;
        return;
    }
    LL sk=0,bs=0;
    hs[k]=0;
    for(int i=fs[k];i;i=nt[i])
    {
        int p=dt[i];
        if(p!=fa)
        {
            dp(p,k);
            (sk+=f[p])%=mo,(bs+=b[p])%=mo,(hs[k]+=h[p])%=mo;
        }
    }
    LL ny=ksm((rd[k]-sk+mo)%mo,mo-2);
    h[k]=ksm((rd[k]-hs[k]+mo)%mo,mo-2);
    f[k]=ny;
    b[k]=((c[k]==1)?((rd[k]+bs)%mo*ny%mo):(bs*ny%mo));
}
void dfs(int k,int fa,LL v)
{
    if(k==1) g[k]=0;
    else g[k]=ksm(((rd[fa]-hs[fa]+h[k]-g[fa])%mo+mo)%mo,mo-2);
    if(k!=1) v=v*g[k]%mo;
    if(c[k]==0) ans+=v;
    for(int i=fs[k];i;i=nt[i])
    {
        int p=dt[i];
        if(p!=fa) dfs(p,k,v);
    }
}
int main()
{
    freopen("0.in","r",stdin);
    freopen("0.out","w",stdout);
    scanf("%d\n",&n);
    fo(i,1,n)
    {
        char ch=getchar();
        c[i]=(ch=='1');
    }
    fo(i,1,n-1)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        link(x,y),link(y,x),rd[x]++,rd[y]++;
    }
    dp(1,0);
    dfs(1,0,1);
    printf("%lld\n",(b[1]+ans)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值