luogu P6835 概率DP 期望

luogu P6835 概率DP 期望

洛谷 P6835

原题链接

题意

n + 1个节点,第i个节点都有指向i + 1的一条单向路,现在给他们添加m条边,每条边都从一个节点指向小于等于自己的一个节点,现在从1号点开始走,每次等概率地选择出边,问到达n+1的步数期望

思路
  • F i , j F_{i,j} Fi,j 代表从i到j的期望步数

  • 由于期望的线性性质,所以 F i , k + F k , j = F i , j F_{i,k} + F_{k,j} = F_{i,j} Fi,k+Fk,j=Fi,j 所以我们算出每个 F i , i + 1 F_{i,i+1} Fi,i+1 最后输出前缀和sum[n]即可

  • 对于当前节点i,出度为 d i d_i di, 所以选某条边的概率为
    1 d i \frac{1}{d_i} di1

  • 选直接连接i+1的那条边,步数为1,即期望步数为1 / d,而选择其他边的期望步数为
    ∑ j ∈ v [ i ] ( 1 + F j , i + F i , i + 1 ) d i \frac{\sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})}}{d_i} dijv[i](1+Fj,i+Fi,i+1)

  • 上面式子像是一个“递归式”,左右都有我们希望计算的 F i , i + 1 F_{i,i+1} Fi,i+1,整理如下:

F i , i + 1 = 1 d i + ∑ j ∈ v [ i ] ( 1 + F j , i + F i , i + 1 ) d i F_{i,i+1} = \frac{1}{d_i} + \frac{\sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})}}{d_i} Fi,i+1=di1+dijv[i](1+Fj,i+Fi,i+1)

d i ∗ F i , i + 1 = 1 + ∑ j ∈ v [ i ] ( 1 + F j , i + F i , i + 1 ) d_i * F_{i,i+1} = 1 + \sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})} diFi,i+1=1+jv[i](1+Fj,i+Fi,i+1)

d i ∗ F i , i + 1 = 1 + d i − 1 + ( d i − 1 ) ∗ F i , i + 1 + ∑ j ∈ v [ i ] F j , i d_i * F_{i,i+1} = 1 + d_i - 1 + (d_i - 1) * F_{i,i+1} + \sum_{j\in v[i]}{F_{j,i}} diFi,i+1=1+di1+(di1)Fi,i+1+jv[i]Fj,i

F i , i + 1 = d i + ∑ j ∈ v [ i ] F j , i F_{i,i+1} = d_i + \sum_{j\in v[i]}{F_{j,i}} Fi,i+1=di+jv[i]Fj,i

其中 F j , i F_{j,i} Fj,i 可以前缀和得到,最终复杂度为线性

注意:

  • 前缀和取模处理难免有后面的值小于前面的时候,所以每次相减时要加一个mod防止变为负数
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

const long long modd = 998244353;

int n, m;
long long ff[1000005] = {0};
vector<int> vv[1000005];
long long su[1000005] = {0};

int main()
{
	int id;
	scanf("%d%d%d", &id, &n, &m);
	for (int i = 1; i <= m; ++i)
	{
		int xx, yy;
		scanf("%d%d", &xx, &yy);
		vv[xx].push_back(yy);
	}
	for (int i = 1; i <= n; ++i)
	{
		long long d = vv[i].size();
		ff[i] = d + 1;
		for (unsigned int j = 0; j < d; ++j)
		{
			ff[i] = (ff[i] % modd + (su[i - 1] - su[vv[i][j] - 1] + modd) % modd) % modd;
		}
		su[i] = (su[i - 1] % modd + ff[i] % modd) % modd;
	}
	printf("%lld\n", su[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值