1. 程式人生 > 實用技巧 >luogu P6835 概率DP 期望

luogu P6835 概率DP 期望

luogu P6835 概率DP 期望

洛谷 P6835

原題連結

題意

n + 1個節點,第i個節點都有指向i + 1的一條單向路,現在給他們新增m條邊,每條邊都從一個節點指向小於等於自己的一個節點,現在從1號點開始走,每次等概率地選擇出邊,問到達n+1的步數期望

思路
  • \(F_{i,j}\) 代表從i到j的期望步數

  • 由於期望的線性性質,所以 \(F_{i,k} + F_{k,j} = F_{i,j}\) 所以我們算出每個 \(F_{i,i+1}\) 最後輸出字首和sum[n]即可

  • 對於當前節點i,出度為 \(d_i\), 所以選某條邊的概率為

\[\frac{1}{d_i} \]

  • 選直接連線i+1的那條邊,步數為1,即期望步數為1 / d,而選擇其他邊的期望步數為

\[\frac{\sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})}}{d_i} \]

  • 上面式子像是一個“遞迴式”,左右都有我們希望計算的\(F_{i,i+1}\),整理如下:

\[F_{i,i+1} = \frac{1}{d_i} + \frac{\sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})}}{d_i} \]

\[d_i * F_{i,i+1} = 1 + \sum_{j\in v[i]}{(1 + F_{j,i} + F_{i,i+1})} \]

\[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}} \]

\[F_{i,i+1} = d_i + \sum_{j\in v[i]}{F_{j,i}} \]

其中 \(F_{j,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;
}