期望学习记录

  • Post author:
  • Post category:其他


期望dp学习:

类型1: 与图结合:

一般套路为dp[i]代表此时i到终点的期望,我们将图反向建立 并进行dp

1.例题:

P4316 绿豆蛙的归宿 – 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

考虑此时假设有一条i->j的边此时我们知道dp[j]的价值 可以发现dp[j]+e[i].dis 为这条边的期望

我们将其/k代表这条边的贡献

所以:

dp[i]=sum(dp[j]+e[i->j].dis)/k

至于遍历的顺序我们考虑拓扑就好了:

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 1e5 + 10;
double dp[maxn];
struct node
{
	int to, next;
	int dis;
}e[maxn<<1];
int head[maxn], cnt,in[maxn],degree[maxn];
void add(int u, int v, int dis)
{
	e[++cnt].dis = dis;
	e[cnt].next = head[u];
	head[u] = cnt;
	e[cnt].to = v;
}
queue<int>q;
void find()
{
	while (!q.empty())
	{
		int u = q.front();
		q.pop();
		for (int i = head[u]; i; i = e[i].next)
		{
			int y = e[i].to;
			dp[y] += ((double)e[i].dis + dp[u]) / ((double)degree[y]);
			in[y]--;
			if (in[y] == 0)
				q.push(y);
		}
	}
}
int main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		in[x]++, degree[x]++;
		add(y, x, z);
     }
	for (int i = 1; i <= n; i++)
	{
		if (in[i] == 0)
		{
			q.push(i);
		}
	}
	find();
	printf("%.2lf", dp[1]);
}


走路3 – 题目 – Daimayuan Online Judge

同类型的题目:

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
int dp[maxn];
const int mod = 1e9 + 7;
int qpow1(int a, int b)
{
	int res =1;
	while (b)
	{
		if (b%2){
			res = res * a%mod;
		}
		a = a*a%mod;
		b >>= 1;
	}
	return res;
}
struct node
{
	int to, next;
	int dis;
}e[maxn << 1];
int head[maxn], cnt, in[maxn], degree[maxn];
void add(int u, int v, int dis)
{
	e[++cnt].dis = dis;
	e[cnt].next = head[u];
	head[u] = cnt;
	e[cnt].to = v;
}
queue<int>q;
void find()
{
	while (!q.empty())
	{	
		int u = q.front();
		q.pop();
		for (int i = head[u]; i; i = e[i].next){
			int y = e[i].to;
			dp[y] +=(dp[u]+1)*qpow1(degree[y],mod-2)%mod;
			dp[y] %= mod;
			in[y]--;
			if (in[y] == 0)
				q.push(y);
		}
	}
}
signed main()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int x, y;
		int z = 1;
		cin >> x >> y;
		in[x]++, degree[x]++;
		add(y, x, z);
	}
	q.push(n);
	find();
	cout << dp[1];
}

类型2:这类型的题目有个共同特点:现在有多少 剩下多少的情况到达终点要多少步

要注意的是我们要求转移时考虑我们此时的这一步


瓜子 – 题目 – Daimayuan Online Judge

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 2e3 + 10;
int dp[maxn][maxn<<1];//dp[i][j]代表此时还剩i个瓜子 且里面有j个瓜子壳到终点的步数
const int mod = 998244353;
int n;
int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1)
		{
			res = res * a%mod;
		}
		b >>= 1;
		a = a * a%mod;
	}
	return res;
}
int sum1[maxn*3];
void init()
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = 0; j <= 2 * (n - i); j++)//2*(n-i)代表此时最多的瓜子壳数量
		{
			int sum = i + j;
			dp[i][j] = (j >= 1 ? dp[i][j - 1] * j%mod*sum1[sum]% mod : 0) + dp[i - 1][j + 2] * i%mod*sum1[sum]% mod+1;
		}
	}
}
signed main()
{
	cin >> n;
	for (int i = 1; i <= 3 * n; i++)
		sum1[i] = qpow(i, mod - 2) % mod;
	init();
	cout << dp[n][0];
}

例题二:牛客2022的第一场一道题

期望的技巧:

算式:

1.
sum\sum
k*(p)^(k-1)=
\sum
(p^k)(求导)=1/(1-p)(当且仅当p<1)

技巧一:

Problem – 453A – Codeforces

我们假设考虑此时投掷到i的概率:p(x==i)

这个p(x==i)=p(x<=i)-p(x<=i-1)

运用此来计算:

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
double qpow(double a, int b)
{
	double res = 1;
	while (b)
	{
		if (b & 1)
		{
			res = res * a;
		}
		b >>= 1;
		a = a * a;
	}
	return res;
}
signed main()
{
	int n, m;
	cin >> m >> n;
	double ans = 0;
	for (int i = 1; i <= m; i++)
	{
		ans += (i*((qpow((double)i / m, n)) - qpow((double)(i - 1) / m, n)));
	}
	printf("%.6lf", ans);
}

类型3:如何处理dp[i][j]在转移的过程中会枚举到自己的问题?


I-你最爱的概率_2022广西师范大学暑期训练赛(重现赛) (nowcoder.com)

我们可以发现此时我们dp[i][j] 转移的话 右边也会出现dp[i][j]

处理方法:

其实我们只要将右边移项到左边就可以将dp[i][j]彻底分离开来了

习题练习:

1.kuangbin带你飞

1.

A Dangerous Maze – LightOJ 1027 – Virtual Judge (csgrandeur.cn)

其实这道题就有一系列概率dp的涉及到的东西:

dp[i]=….dp[i]

这个类型:

我们考虑对于这道题如何处理:

dp[i]代表此时从i门出去需要等待的时间:

对于正数:

dp[i]=a[i]

对于负数:

dp[i]=abs(a[i])+dp[0];

dp[0]=sum(dp[1]+dp[2]…dp[n])/n

我们将上述式子化简即可得到结果

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 9e6 + 10;
const int mod = 1e9 + 7;
int a[maxn];
int gcd(int a, int b)
{
	if (b == 0)
		return a;
	else
		return gcd(b, a%b);
}
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n;
		cin >> n;
		int sum = 0, num = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			if (a[i] < 0)
			{
				num++;
				sum += abs(a[i]);
			}
			else
			{
				sum += a[i];
			}
		}
		if (n-num == 0)
		{
			cout << "Case " << ++T << ": ";
			cout << "inf" << endl;
			continue;
		}
		int k = gcd(sum, n - num);
		cout << "Case " << ++T << ": ";
		cout << sum/k << '/' << (n - num)/k << endl;
	}
}


Discovering Gold – LightOJ 1030 – Virtual Judge (csgrandeur.cn)

这道题是概率dp中很典型的例子:逆推:

我们设dp[i]代表此时从i点到n期望的分数:

考虑i可以到前面哪几个数 设这些数为n

dp[i]=sum(dp[i+1]+dp[i+2]+dp[i+3]..+dp[i+n])/n

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 110;
double dp[maxn];
double a[maxn];
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n;
		cin >> n;
		for (int i = 1; i <= n; i++)cin >> a[i];
		dp[n] = a[n];
		for (int i = n - 1; i >= 1; i--)
		{
			dp[i] = 0;
			int cnt = min(1ll*6, n - i);
			for (int j = 1; j <= cnt; j++)
			{
				dp[i] += (dp[i + j] + a[i]) / cnt;
			}
		}
		cout << "Case " << ++T << ": ";
		printf("%.6lf\n", dp[1]);
	}

}


Race to 1 Again – LightOJ 1038 – Virtual Judge (csgrandeur.cn)

这道题可以看做期望dp的轮数问题

这类问题就是要加个1就好了:

我们预处理出所有数的约数:

dp[i]代表i到1 的次数

dp[i]=(dp[i1]+dp[i2]+….dp[in])/n+1:(n为约数个数)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define int long long
const int maxn = 1e5+10;
double dp[maxn];
double a[maxn];
vector<int>p[maxn];
void init()
{
	for (int i = 1; i <= 1e5; i++)
	{
		for (int j = 1; j*i <= 1e5; j++)
		{
			p[i*j].push_back(i);
		}
	}
	dp[0] = 0;
	for (int i = 2; i <= 1e5; i++)
	{
		int len = p[i].size();
		dp[i] = len;
		for (auto x : p[i])
		{
			if (x == i)
				continue;
			dp[i] += dp[x];
		}

		dp[i] /= (len - 1);
	}
}
signed main()
{
	init();
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n;
		cin >> n;
		cout << "Case " << ++T << ": ";
		printf("%.6lf\n", dp[n]);
	}

}


Just another Robbery – LightOJ 1079 – Virtual Judge (csgrandeur.cn)

这道题与其所是概率dp更像是背包:

dp[i]代表我抢i元最大不被抓的概率

不被抓的概率就是所有不被抓的乘积

加下来就是背包模板了

/*
1.背包:
dp[i]代表在强到i元的最大不被抓概率
*/
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 10010;
double dp[maxn];
int a[maxn];
double d[maxn];
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		memset(dp, 0, sizeof dp);
		double maxx; int  n;
		cin >> maxx >> n;
		dp[0] = 1;
		for (int i = 1; i <= n; i++)
		{		
			cin >> a[i] >> d[i];
			d[i] = 1 - d[i];
		}
		for (int i = 1; i <= n; i++)
		{
			for (int j = 10000; j >= a[i]; j--)
			{
				if (dp[j - a[i]])
				{
					dp[j] = max(dp[j], dp[j - a[i]] * d[i]);
				}
			}
		}
		maxx = 1 - maxx;
		int ans = 0;
		for (int i = 10000; i >= 0; i--)
		{
			if (dp[i]>=maxx)
			{
				ans = i;
				break;
			}
		}
		cout << "Case " << ++T << ": " << ans << endl;
	}
}


Birthday Paradox – LightOJ 1104 – Virtual Judge (csgrandeur.cn)

这道题其实转化来就是:

Anx/ n^x<=1/2

由于此时我们可以发现当x不断增加的话 该式子不断减小 所以我们可以枚举其来处理该问题

至于不用二分的原因在于 A太大了 可能会导致错误

枚举可行的原因在于x一般比n小很多 案例就可以看出来

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n;
		cin >> n;
		int ans = 0;
		double now = 1;
		for (int i = 1; i <= n; i++)
		{
			now *= double(n - i) / n;
			if (now <= 0.5)
			{
				ans=i;
				break;
			}
		}
		cout << "Case " << ++T << ": " << ans << endl;
	}
}


Snakes and Ladders – LightOJ 1151 – Virtual Judge (csgrandeur.cn)

这道题是之前那道题的加强版 加强在于一般dp处理不了的问题 :

就是我求dp[i] 会用到dp[j] 且dp[j]的求解过程 会用到dp[i]

这道题要利用到高斯消元不过我还没学 学完了再回来补题:


Dice (III) – LightOJ 1248 – Virtual Judge (csgrandeur.cn)

这道题我们可以利用dp[i]代表我看了i面的期望次数:

dp[i]=dp[i-1]*(i-1)/n+dp[i]*(n-i+1)/n+1;

化简一下就好:

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
double dp[maxn];
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n;
		cin >> n;
		dp[n] = 0;
		for (int i = n - 1; i >= 0; i--)
		{
			dp[i] = dp[i + 1] + double(n) / (n - i);
		}
		cout << "Case " << ++T << ": ";
		printf("%.6lf\n", dp[0]);
	}
}


Island of Survival – LightOJ 1265 – Virtual Judge (csgrandeur.cn)

这道就是比较典型的每次在两者之间选择的题目了

我们假设dp[i][j]代表此时还剩i个老虎 j个🦌 接下来就按他讲的转移就好了

dp[0][1-n]为1

要注意的是:

1、我们人也要算到其中:

2、对于情况4 我们要分别考虑并选择最大值

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
const int maxn = 1e3 + 10;
double dp[maxn][maxn];
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int a, b;
		cin >> a >> b;
		for (int i = 0; i <= b; i++)
		{
			dp[0][i] = 1;
		}
		for (int i = 1; i <= a; i++)
		{
			for (int j = 0; j <= b; j++)
			{
				double now = 0;
				if (i >= 2)
				{
					now += (double(i*(i - 1)) / ((i + j + 1)*(i + j)))*dp[i - 2][j];
				}
				if (j >= 1)
				{
					now += double(2 * i*j) / ((i + j + 1)*(i + j))*dp[i][j - 1];
				}
				if (j >= 2)//代表此时可以抽到2只
				{
					double now1 = now, now2 = now;
					now1 += double(2*j) / ((i + j + 1)*(i + j))*dp[i][j - 1];
					dp[i][j] = now1 / (1 - (double(j*j - j) / ((i + j + 1)*(i + j))));
					dp[i][j] = max(dp[i][j], now2 / double(1 - (double(j*j + j) / ((i + j + 1)*(i + j)))));
				}
				else if (j == 0)
				{
					dp[i][j] = now;
				}
				else
				{
				  double now1 = now, now2 = now;
					now1+=((double(2*j))/((i + j + 1)*(i + j)))*dp[i][j - 1];
					dp[i][j] = now1;
					dp[i][j] = max(dp[i][j], now2 / double(1 - double(2 * j) / ((i + j + 1)*(i + j))));
				}
			}
		}
		
		cout << "Case " << ++T << ": ";
		printf("%.6lf\n", dp[a][b]);
	}

}


Beating the Dataset – LightOJ 1274 – Virtual Judge (csgrandeur.cn)

这道题是期望dp的一个线性问题:

我们考虑dp[i][j][k]i代表此时是第i位置 j代表此时已经有了j个yes 且此时答案为k :0代表对 1 代表错

我们要考虑逆序处理:原因在于我们此时k的答案决定了我们下一次是对还是错

dp[i][j][0]= (dp[i+1][j][1]+1)*(n-i-(YES-j))/(n-i)+dp[i+1][j+1][0]*(YES-j)/(n-i);

dp[i][j][1]同理:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
#define int long long
const int maxn = 1e5 + 10;
double dp[2][5100][2];
signed main()
{
	int t;
	cin >> t;
	int T = 0;
	while (t--)
	{
		int n, m;
		cin >> n >> m;
		int cnt0 = m-2*n, cnt1 = 3*n-m;//0代表此时正确 1代表此时错误
		dp[n & 1][cnt0][1] = dp[n & 1][cnt0][0] = 0;
		for (int i = n - 1; i >= 0; i--) 
		{
			int ret = n - i,now=i&1,nxt=(i+1)&1;
			memset(dp[now], 0, sizeof dp[now]);
			for (int j = min(cnt0, i); j >=max(0ll,i-cnt1); j--)
			{
				double p1 = 1.0*(cnt0 - j) / ret, p2 = 1.0*(n - i - (cnt0 - j)) / ret;
				dp[now][j][0] = dp[nxt][j + 1][0] * p1 + (dp[nxt][j][1] + 1)*p2;
				dp[now][j][1] = (dp[nxt][j + 1][0] + 1)*p1 + dp[nxt][j][1] * p2;
			}
		}
		cout << "Case " << ++T << ": ";
		printf("%.6lf\n", dp[0][0][0]);
	}
}



版权声明:本文为qq_62105443原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。