dp 求概率
这类题目采用顺推,也就是从初始状态推向结果。同一般的 DP 类似的,难点依然是对状态转移方程的刻画,只是这类题目经过了概率论知识的包装。---OI
走路2 - 题目 - Daimayuan Online Judge
用vector<int>h[N]来存储每一个点能够通向的点,那么该点去往其他点的概率
p = 1/h[i].size()
在从起点开始遍历每一个点,到达一个点的概率为其父亲节点的概率和
ps:题目要求取模,运用逆元 ,可预处理逆元,优化时间
inline void slove()
{
int n = read(),m=read();
V<int>h[101];
for (int i = 1; i <= m; i++)
{
int x=read(), y=read();
h[x].push_back(y);
}
//自己走到自己概率一定为1
dp[1] = 1;
for (int i = 1; i < n; i++)
{
int m = h[i].size();
for (int j = 0; j < m; j++)
{
//h[i][j]为去往的点 通往每个点的概率为 (1/m)%mod
dp[h[i][j]] += dp[i] * ksm(m, mod - 2);
dp[h[i][j]] %= mod;
}
}
printf("%lld", dp[n]);
}
dp求期望
(从终点逆推到起点)
走路3 - 题目 - Daimayuan Online Judge
对于从起点开始顺推,情况多种,不一定能到达终点,采取逆推方式更简单
对于每一个点的期望 E=dp[i] = Σ(1->m) (dp[j]+1)/m = (Σ(1->m) dp[j]/m )+ 1;
inline void slove()
{
int n = read(),m=read();
V<int>h[101];
for (int i = 1; i <= m; i++)
{
int x=read(), y=read();
h[x].push_back(y);
}
//求期望是 终点—>起点的顺序
//dp[n] 路数是0 dp[n]=0;
for (int i = n-1; i; i--)
{
int m = h[i].size();
// i—>j 的概率是 1/m 路数是 j->n的路数加 1
dp[i] = 1;
for (int j : h[i])
{
dp[i] += dp[j] * ksm(m, mod - 2);
dp[i] %= mod;
}
}
cout <<dp[1] << endl;
}
很多期望dp可采取类似的理解来解决
类似的理解ps:一个点他有多个子节点,要遍历所有子节点来求出其期望
例如:
瓜子 - 题目 - Daimayuan Online Judge
在第二层for循环中,遍历的是 第i个瓜子找到后,还剩下多少个瓜子壳(最多2*n-2*i个瓜子壳,运气很好,全拿的瓜子,没有丢弃瓜子壳 。最少为0 运气很差,一直在丢吃过的瓜子的壳)
// 剩下 i 个瓜子,j 个瓜子壳 期望多少次拿完 dp[0][j]=0
ll dp[N][2 * N];
int a[2 * N];
inline ll ksm(ll a, int b)
{
ll ans = 1;
while (b) {
if (b & 1)
ans = (ans * a) % mod;
a = (a * a) % mod;
b /= 2;
}
return ans;
}
inline void slove()
{
int n = read();
for (int i = 1; i <= 2 * n; i++)
a[i] = ksm(i, mod - 2);
for (int i = 1; i <= n; i++)
{
//遍历 第i个瓜子后 有多少个子情况
for (int j = 0; j<=2*n-2*i; j++)
{
//拿到了瓜子,剩下瓜子数-1 瓜子壳+2
dp[i][j] += dp[i - 1][j + 2] * i % mod * (ll)a[i + j] % mod;
dp[i][j] = (dp[i][j] + 1) % mod;
if (j)
//拿到瓜子壳,剩下瓜子壳-1
dp[i][j] += dp[i][j - 1] * j % mod * (ll)a[i + j] % mod;
}
}
cout << dp[n][0] << endl;
}