2023大厂真题提交网址(含题解):
www.CodeFun2000.com(http://101.43.147.120/)
最近我们一直在将收集到的机试真题制作数据并搬运到自己的OJ上,供大家免费练习,体会真题难度。现在OJ已录入50+道2023年最新大厂真题,同时在不断的更新。同时,可以关注"塔子哥学算法"公众号获得每道题的题解。
前言:
对于期望dp/概率dp,我们有状态以及状态转移方程.我们将所有的状态看成未知数,状态转移方程看成线性方程。那么就得到了一个线性方程组.
适用情形:
状态转移有后效性时
优化点:
大多数情况下它会是一个稀疏矩阵(因为一个转移方程所牵涉到的状态比较少).所以在 消元的时候,遇到0元素就continue. 这个trick可以将复杂度从三次方优化到近似二次方.(前提是要是一个稀疏矩阵.)
题目一:HDU4780
题目大意:
你有两个数 A , B A,B A,B。 每次你会选择 m i n ( A , B ) min(A,B) min(A,B)。然后有 p p p的概率使得其 + 1 +1 +1.有 1 − p 1-p 1−p的概率使得其 − 2 -2 −2.(不足 2 2 2,那么将其归 0 0 0).问你期望多少次使得其有一个数达到 20 20 20.
题目思路:
方法一:暴力高斯消元
令 f ( i , j ) ( i ≥ j ) f(i,j)\ \ (i \geq j) f(i,j) (i≥j)代表从 ( i , j ) (i,j) (i,j)到其中一个数达到20所期望的次数.
根据期望的逆推法,我们有:
f
(
20
,
x
)
=
0
,
x
∈
[
0
,
20
]
f(20,x)=0 \ ,\ x \in [0,20]
f(20,x)=0 , x∈[0,20]
f ( i , j ) = 1 + p ∗ f ( i , j − 1 ) + ( 1 − p ) ∗ f ( i , j + 2 ) f(i,j) = 1+p*f(i,j-1)+(1-p)*f(i,j+2) f(i,j)=1+p∗f(i,j−1)+(1−p)∗f(i,j+2)
因为这个转移方程有后效性,所以我们使用高斯消元解方程组.将状态 ( i , j ) (i,j) (i,j)编号.列出含有 i ∗ j i*j i∗j个未知数, i ∗ j i*j i∗j个方程的方程组,跑高斯消元即可.又因为显然这些转移方程是线性无关的,所以一定有解,且只有唯一解.
优化的时候还要注意一点,在 f a b s ( . . ) ≤ e p s fabs(..) \leq eps fabs(..)≤eps的时候,一定要注意 e p s eps eps的精度够不够。因为有时候结果很大是因为除法的分母很小。所以要注意 e p s eps eps的精度要够大.
时间复杂度: O ( u p 6 ) O(up^6) O(up6) 由于是稀疏矩阵,趋近于 O ( u p 4 ) O(up^4) O(up4)
1.long double 3.4s
2.double 0.8s
3.根据稀疏矩阵加优化 0.1s
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const double eps = 1e-12;
map<pii , int> mp;
double a[300][300];
int cnt = 0;
int up = 20;
int main()
{
for (int i = up ; i >= 0 ; i--)
for (int j = i ; j >= 0 ; j--)
mp[make_pair(i,j)] = ++cnt;
double p;
while (~scanf("%lf" , &p)){
// 初始化边界条件: f(20 , x) = 0
for (int i = 0 ; i <= cnt ; i++)
for (int j = 0 ; j <= cnt + 1; j++)
a[i][j] = 0.0;
for (int i = 0 ; i <= up ; i++){
int x = mp[make_pair(up , i)];
a[x][x] = 1;
}
for (int i = up - 1 ; i >= 0 ; i--)
for (int j = i ; j >= 0 ; j--){
// f(i,j) = 1 + f(i , j + 1) * p + f(max(i , j - 2) , min(i , j - 2)) * (1 - p)
// f(i,j) - p * f(i , j + 1) - (1 - p) * f(max(i , j - 2) , min(i , j - 2)) = 1
int x = mp[make_pair(i , j)];
int y = mp[make_pair(max(i , j + 1) , min(i , j + 1))];
int z = mp[make_pair(max(i , max(j - 2 , 0)) , min(i , max(j - 2 , 0)))];
a[x][x] += 1;
a[x][y] += -p;
a[x][z] += -(1 - p);
a[x][cnt + 1] = 1;
}
// 构造好系数矩阵后跑高斯消元 稀疏矩阵的复杂度很低
int r , c;
for (r = c = 1 ; r <= cnt && c <= cnt ; r++ , c++){
int p = r;
for (int j = r + 1 ; j <= cnt ; j++){
if (fabs(a[j][c]) > fabs(a[r][c])){
p = j;
}
}
// 会不会有自由元呢? 不会的
if (fabs(a[p][c]) < eps) {
r--;
continue;
}
swap(a[r] , a[p]);
for (int j = 1 ; j <= cnt ; j++){
if (j == r) continue;
if (fabs(a[j][c]) < eps) continue; // 对付稀疏矩阵的trick
double d = a[j][c] / a[r][c];
for (int k = c ; k <= cnt + 1 ; k++)
a[j][k] -= d * a[r][k];
}
}
int e = mp[make_pair(0,0)];
double ans = a[e][cnt + 1] / a[e][e];
printf("%.6f\n" , ans);
}
return 0;
}
进一步优化:
由于取 m i n min min的特点,状态的变化一定是 f ( 0 , 0 ) → f ( 0 , 1 ) → f ( 1 , 1 ) → f ( 1 , 2 ) − > → f ( 2 , 2 ) → . . . → f ( 19 , 20 ) f(0,0) \rightarrow f(0,1) \rightarrow f(1,1) \rightarrow f(1,2) -> \rightarrow f(2,2) \rightarrow ... \rightarrow f(19,20) f(0,0)→f(0,1)→f(1,1)→f(1,2)−>→f(2,2)→...→f(19,20)
可以看出花费就是一个到达19分,一个到达20分的花费,等于 2 ∗ f ( 0 ) − f ( 19 ) 2*f(0)-f(19) 2∗f(0)−f(19)(两个到达20的花费减去一个从19到20的期望花费).
状态变化的过程不难看出:两个维度的变化是互不相干的。那么这个问题拓展到 k k k维也一样.
在 k k k维的情况下,复杂度就是 k ∗ f ( 0 ) − ( k − 1 ) ∗ f ( 19 ) k*f(0)-(k-1)*f(19) k∗f(0)−(k−1)∗f(19)
复杂度与k无关,为 O ( u p 3 ) O(up^3) O(up3)
方法二:递推法绕开高斯消元,解除后效性 , 线性复杂度
这题还能使用递推法,那么分析过程就很像我之前AK那一场的题了:牛客OI周赛14-普及组
而且思想在:wannay-概率dp 视频中敦爷也介绍过了.
思路:
无论我们怎么反复横跳,过程也许很波折,但是大方向一定是从前往后慢慢进行的。所以我们不妨将这个问题分阶段进行。令 t i t_i ti代表第一次到达 i i i分数的期望次数.根据上述优化可知 t 20 + t 19 t_{20}+t_{19} t20+t19所求.
考虑转移:
1.固定部分:想要到 i i i.先得到 i − 1 i-1 i−1.花费 = f i − 1 + 1 = f_{i-1}+1 =fi−1+1.
解释: f i f_i fi是显然的,而 + 1 +1 +1是因为,我们无论这次到或者不到,都必须试一下。所以 + 1 +1 +1
2.概率产生的花费:
有 p p p的概率成功,成功了,那无事发生,花费包含在固定部分了(可以考虑 p = 1 p=1 p=1的极端情况.)
有 1 − p 1-p 1−p的概率失败。失败后我们到达状态 i − 3 i-3 i−3.从 i − 3 到 i i-3到i i−3到i的花费可以表示成: d i = f i − f i − 3 d_i=f_{i}-f_{i-3} di=fi−fi−3
所以 f i = f i − 1 + 1 + ( 1 − p ) ∗ ( f i − f i − 3 ) f_i=f_{i - 1}+1+(1-p)*(f_i-f_{i-3}) fi=fi−1+1+(1−p)∗(fi−fi−3)
⟹ f i = f i − 1 + ( p − 1 ) ∗ f i − 3 + 1 p \implies f_i=\frac{f_{i-1}+(p-1)*f_{i-3}+1}{p} ⟹fi=pfi−1+(p−1)∗fi−3+1
f
0
=
0
f_0=0
f0=0
f
1
=
1
p
f_1=\frac{1}{p}
f1=p1
f
2
=
f
1
+
(
p
−
1
)
∗
f
0
+
1
p
=
1
p
+
1
p
2
f_2=\frac{f_{1}+(p-1)*f_{0}+1}{p}=\frac{1}{p}+\frac{1}{p^2}
f2=pf1+(p−1)∗f0+1=p1+p21
时间复杂度: O ( n ) O(n) O(n)