-概率DP

目录

1、走路1

2、走路2

3、走路3

4、瓜子

5、走路4

6、守卫者的挑战


1、走路1

// problem: 走路1

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
double f[105];
vector<int> c[105];
int n, m;
int main(){
	scanf("%d %d",&n, &m);
	for(int i = 1; i <= m; ++i){
		int x, y;
		scanf("%d %d", &x, &y);
		c[x].push_back(y);
	}
	memset(f, 0, sizeof(f));
	f[1] = 1;
	for(int i = 1; i < n; ++i){
		int l = c[i].size();
		for(int j = 0; j < l; ++j)
			f[c[i][j]] += f[i] / l;
	}

	printf("%.10lf\n", f[n]);

	return 0;
}
/*
相信某些人可能存在某些疑问,为什么i号城市是从 1 - n 开始枚举的,可能1号城市
一开始只走向了 5 号城市呀。 为什么不用 dfs搜索?  用dfs搜索明显更好,但是如果你认为 循环
枚举有问题的话, 其实你这还是没有理解好题意。
就算一开始只走向了 5 号城市,那说明 1 -> 2 是走不通的,概率自然也为 0
*/

2、走路2

// problem :  走路2
/*
知识点:
(a / b) mod p  ==>  a * b^(p-2)  mod p         费马小定理

b^(p-2)  mod p  可以用快速幂求
*/
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
ll f[105];
vector<int> c[105];
int n, m;
const int p = 1e9 + 7;

ll rp(ll now, int k){
	ll res = 1;
	for(; k; k >>= 1, now *= now, now %= p)
		if(k & 1)
			res *= now, res %= p;
	return res;
}

int main(){
	scanf("%d %d",&n, &m);
	for(int i = 1; i <= m; ++i){
		int x, y;
		scanf("%d %d", &x, &y);
		c[x].push_back(y);
	}
	memset(f, 0, sizeof(f));
	f[1] = 1;
	for(int i = 1; i < n; ++i){
		int l = c[i].size();
		ll k = rp(l, p - 2);
		for(int j = 0; j < l; ++j)
			f[c[i][j]] += f[i] * k,
			f[c[i][j]] %= p;
	}

	printf("%lld\n", f[n]);

	return 0;
}

3、走路3

// problem :  走路3

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
ll f[105][105];  //位于  i 号城市,走了 j 条路的概率
vector<int> c[105];
int n, m;
const int p = 1e9 + 7;

ll rp(ll now, int k){
	ll res = 1;
	for(; k; k >>= 1, now *= now, now %= p)
		if(k & 1)
			res *= now, res %= p;
	return res;
}

int main(){
	scanf("%d %d",&n, &m);
	for(int i = 1; i <= m; ++i){
		int x, y;
		scanf("%d %d", &x, &y);
		c[x].push_back(y);
	}
	memset(f, 0, sizeof(f));
	f[1][0] = 1; // f 存的是概率 还不是期望
	for(int i = 1; i < n; ++i){
		int l = c[i].size();
		for(int j = 0; j < n; ++j)
			if(f[i][j])
				for(int k = 0; k < l; ++k)
					f[c[i][k]][j + 1] += f[i][j] * rp(l, p - 2),
					f[c[i][k]][j + 1] %= p;
	}

	ll ans = 0;
	for(int i  = 1; i < n; ++i)
		ans += f[n][i] * i, ans %= p;
	printf("%lld\n", ans);
	return 0;
}

可以进行优化, 从后往前求

 用f[x] 表示从x号城市出发  期望  经过多少条高速公路能够到达n号城市,f[n] = 0;
  x - > y  (1步)    y - > n    (f[y])
 求概率,起点到终点    求期望,终点到起点

// problem :  走路3  快速版
// 用f[x] 表示从x号城市出发  期望  经过多少条高速公路能够到达n号城市,f[n] = 0;
//   x - > y  (1步)    y - > n    (f[y])
// 求概率,起点到终点    求期望,终点到起点

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
ll f[105];  //位于  i 号城市,走了 j 条路的期望
vector<int> c[105];
int n, m;
const int p = 1e9 + 7;

ll rp(ll now, int k){
	ll res = 1;
	for(; k; k >>= 1, now *= now, now %= p)
		if(k & 1)
			res *= now, res %= p;
	return res;
}

int main(){
	scanf("%d %d",&n, &m);
	for(int i = 1; i <= m; ++i){
		int x, y;
		scanf("%d %d", &x, &y);
		c[x].push_back(y);
	}
	memset(f, 0, sizeof(f));
	for(int i = n - 1; i ; --i){  // 数据保证所有城市都可以到 n 号城市。
		int l = c[i].size();
		f[i] = 1;
		for(int j = 0; j < l; ++j)
			f[i] += f[c[i][j]] * rp(l, p - 2),
			f[i] %= p;
	}

	printf("%lld\n", f[1]);
	return 0;
}

4、瓜子

// problem : 瓜子 

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
ll f[2001][4005], a[4005];
const int p = 998244353;
int n;
ll rp(ll now, int k){
	ll res = 1;
	for(; k; k >>= 1, now *= now, now %= p)
		if(k & 1)
			res *= now, res %= p;
	return res;
}

int main(){
	scanf("%d",&n);
	for(int i = 1; i <= 2 * n; ++i)
		a[i] = rp(i, p - 2);
	// f[0][j] = 0;
	for(int i = 1; i <= n; ++i)
		for(int j = 0; j <= 2 * (n - i); ++j){
			f[i][j] = 1;
			f[i][j] += f[i - 1][j + 2] * i % p * a[i + j] % p;
			f[i][j] %= p;
			if(j)
				f[i][j] += f[i][j - 1] * j % p * a[i + j] % p,f[i][j] %= p;

		}
	printf("%lld\n", f[n][0]);
	return 0;
}

5、走路4

 这题的x和y没有大小之间的关系,所以可能存在环。如果存在环,就不满足最优子结构,就不用使用DP做。

该题算是模板题,采用 高斯消去

// problem : 走路4     有环  不满足最优子结构  不能使用动态规划
// 高斯消元 帮你忙!

#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef pair<int, int> PII;
int n, m;
const int p = 1e9 + 7;
vector<int> c[105];
ll f[105][105], v[105], a[105];
ll rp(ll now, int k){
	ll res = 1;
	for(; k; k >>= 1, now *= now, now %= p)
		if(k & 1)
			res *= now, res %= p;
	return res;
}
inline void gsxy(){
	for(int i = 1; i <= n; ++i){
		for(int j = i; j <= n; ++j)
			if(f[j][i]){
				for(int k = i; k <= n; ++k)
					swap(f[i][k], f[j][k]);
				swap(v[j], v[i]);
			}
		for(int j = i + 1; j <= n; ++j)
			if(f[j][i]){
				ll delta = f[j][i] * rp(f[i][i], p - 2) % p;
				for(int k = i; k <= n; ++k){
					f[j][k] -= f[i][k] * delta % p;
					if(f[j][k] < 0)
						f[j][k] += p;
				}
				v[j] -= v[i] * delta % p;
				if(v[j] < 0)
					v[j] += p;
			}
	}
	for(int i = n; i; --i){
		for(int j = i + 1; j <= n; ++j){
			v[i] -= f[i][j] * a[j] % p;
			if(v[i] < 0)
				v[i] += p;
		}
		a[i] = v[i] * rp(f[i][i], p - 2) % p;
	}
}
int main(){
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= m; ++i){
		int x, y;
		scanf("%d %d", &x, &y);
		c[x].push_back(y);
	}
	for(int i = 1; i < n; ++i){
		f[i][i] = 1; v[i] = 1;
		int l = c[i].size();
		for(int j = 0; j < l; ++j)
			f[i][c[i][j]] = p - 1 * rp(l, p - 2);
	}
	f[n][n] = 1;
	gsxy();
	printf("%lld\n", a[1]);

	return 0;
}

6、守卫者的挑战

232. 守卫者的挑战 - AcWing题库https://www.acwing.com/problem/content/234/

分析:第一次做概率DP,题目懂,但是上手就不会写。而且这题的背包可能一开始会变成负数,最后再变成正数,最终也是满足条件的。考虑到这种情况,我们就要将背包的容量进行偏移。

如果有n个任务,那么最极端的情况下,背包会成 -n 的状态,所以我们的偏移量设成 -n 就够了;再考虑到一种情况,如果我们的k很大,其实相当于是浪费背包空间的一种情况,因为我们有n个任务,所以我们真实的背包也只需要n的容量即可。并且有一个最重要的前提就是找到dp的状态方程表示:

f[i][j][k] 表示经过 i 关,通过了 j 关,还剩下 k 的背包容量。

#include<bits/stdc++.h>
using namespace std;
int n,l,k;
const int N = 205;
double f[N][N][N<<1],p[N];
int main(){
    scanf("%d %d %d",&n,&l,&k);
    if(k>n)k = n;
    f[0][0][n+k] = 1;// 偏移n个单位,防止背包先出现负数,再出现正数的情况
    
    for(int i = 1;i<=n;++i){
        scanf("%lf",&p[i]);
        p[i]/=100;
    }
    for(int i = 1;i<=n;++i){
        int a;scanf("%d",&a);// 第i关通过后获得的背包
        for(int j = 0;j<i;++j){// 通关的个数 
            for(int v = 0;v<=n+n;++v){
                if(v+a>=0)f[i][j+1][min(n+n,v+a)] += p[i] * f[i-1][j][v];
                f[i][j][v] += (1-p[i]) * f[i-1][j][v];
            }
        }
    }
    double ans = 0;
    for(int i = l;i<=n;++i)
    for(int j = n;j<=n+n;++j)ans += f[n][i][j];
    printf("%.6lf\n",ans);
    
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xingxg.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值