G - 旅行的意义(概率DP) (DAG图的概率与期望)

为什么有人永远渴望旅行,或许就因为,巧合和温暖会在下一秒蜂拥而至吧。

一直想去旅游的天天决定在即将到来的五一假期中安排一场环游世界的旅行。为此,他已经提前查阅了很多资料,并准备画一张旅游路线图。天天先将所有可能会去的 nn 个旅游城市依次编号标记为 1,2,,n1,2,⋯,n。如果从城市 AA 到城市 BB 有一条直达的铁路线路,他就会在图上画上一条从 AA 向 BB 的有向线段。因为天天不喜欢把时间浪费在往返的乘车上,因此他设计的旅游地图路线是一个有向无环图。

天天身在 11 号城市,他每到达一个旅游城市都会先花一天的时间游玩当地的旅游景点。接下来他也没有明确的目的地,所以第二天他会随机地选择该城市的一条直达线路,花费一天的时间通往下一个旅游城市。当然,如果这个城市的旅游景点太好玩的话,他可能会选择再逗留一天,但是由于假期有限,他在当前的旅游城市最多只能呆 22 天。例如,当天天在城市 CC 时,若城市 CC 有 22条直达线路分别通往城市 AA 和城市 BB,则在第一天的游玩过后,第二天他有 1313 的可能会选择继续逗留在城市 CC 多游玩一天,但是第三天他一定不会再逗留在城市 CC 了;同时他有 1313 可能会选择立即搭乘直达城市 AA 的高铁;他也有 1313 的可能会选择立即搭乘直达城市 BB 的高铁。

当天天把所有的旅游城市都游玩过后,他也就只能结束这段难忘的五一旅行假期了。现在请聪明的你帮天天提前计算一下,他本次旅行时间的期望是多少呢?

容易证明天天旅行时间的期望为 PQPQ 的形式,其中 PP 和 QQ 互质,且 Q0 (𝚖𝚘𝚍 998244353)Q≢0 (mod 998244353)。因此答案请以 PQ1 (𝚖𝚘𝚍 998244353)P⋅Q−1 (mod 998244353) 的形式输出,其中 Q1Q−1 表示 QQ 在取模 998244353998244353 下的逆元。

Input

第一行输入一个正整数 T (1T10)T (1≤T≤10),表示数据组数。接下来 TT 组数据,每组数据均满足:

  • 第一行输入两个非负整数 n (1n105)n (1≤n≤105) 和 m (0m105)m (0≤m≤105),分别表示天天可能旅行的城市数量 nn 和它们之间的直达线路数量 mm。
  • 接下来 mm 行,每行输入两个正整数 uu 和 v (1u,vn)v (1≤u,v≤n),表示从城市 uu 到 vv 有一条单向直达线路,保证两个旅游城市之间最多只有 11 条直达线路。

Output

对于每组数据,请输出一个非负整数,表示天天旅行时间的期望,注意换行。

Example

Input
2
1 0
2 1
1 2
Output
2
499122181

Note

第一组样例只有一个旅游城市。首先,天天会在该城市游玩一天,第二天只剩下一个选择——留下来接着玩一天,再之后他就只能结束旅程了,所以旅游时间的期望是 22。

第二组样例由两个旅游城市,从城市 11 到城市 22 有一条直达的线路。天天首先在城市 11 游玩一天,然后有 1212 的概率前往城市 22,这将花费 11 天时间乘坐高铁;当然天天也有 1212 的概率逗留在城市 11 多玩一天,第三天再乘坐高铁前往城市 22。因此刚到达城市 22 时,天天花费的旅行时间期望是 1+[121+12(1+1)]=2.51+[12⋅1+12⋅(1+1)]=2.5 天。接着天天会在城市 22 先游玩一天,但是接下来他没有其他城市可以去了,只能选择继续逗留一天然后终止旅程,容易算出本次旅程总的时间期望为 4.54.5天,即 92=921 (𝚖𝚘𝚍 998244353)=49912218192=9⋅2−1 (mod 998244353)=499122181。

原博客题解:https://blog.csdn.net/sdut_jk17_zhangming/article/details/89811017

题解:DAG的期望求解,当前点的期望等于该点的贡献加上所有出边节点的贡献,DFS进去,遇到遍历过的直接加上,否则先遍历再加上.

主要是想清楚怎么转移的过程.

 

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=100010;
vector<int>G[maxn];
const int mod=998244353;
ll dis[maxn];
ll quickpow(ll a,ll b)
{
    ll ans=1;
    while(b){
        if(b&1)ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}
ll DFS(int u)
{
    int sz=G[u].size();
    int p1=quickpow(sz+1,mod-2);//第一次选择,有sz+1个选择,加1代表逗留一天
    int p2=p1*quickpow(sz,mod-2)%mod;//第二次选择,只能向sz个出边选择
    ll ans=0;
    if(u==1)dis[u]=1+p1;//首次遍历时当前点的贡献值,1号节点是根节点,无入边,1+p1代表首先游玩一天,然后继续游玩一天的贡献
    else dis[u]=2+p1;//这里与根节点不同的是多加了一个1,代表到达该点必有1条铁路的贡献,这里虽然放1,但回溯上去的时候是乘上概率的
    for(int i=0;i<sz;i++){
        int v=G[u][i];
        if(dis[v])//如果当前节点访问过,就算上贡献
            dis[u]=(dis[u]+(p1+p2)*dis[v])%mod;
        else{
            DFS(v);//否则先算出该点贡献.然后计算本点贡献
            dis[u]=(dis[u]+(p1+p2)*dis[v])%mod;
        }
    }
    return dis[u];
}
int main()
{
    ios::sync_with_stdio(0);
    int T;
    cin>>T;
    while(T--){
        memset(dis,0,sizeof(dis));
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;i++)G[i].clear();
        for(int i=1;i<=m;i++){
            int u,v;
            cin>>u>>v;
            G[u].push_back(v);
        }
        cout<<DFS(1)<<endl;
    }
    return 0;
}

 

posted @ 2019-08-09 21:02  cherish__lin  阅读(528)  评论(0编辑  收藏  举报