今天又做到了一道期望dp的题目,然而,还是犯了一个非常经典的错误,总是用正向推导去做期望dp(似乎这样更符合dp的逻辑),但是对于这个情况,期望dp总是会出现不好处理第一次操作和最后一次操作,我们来从两道题目来窥见这个问题吧~
题意:玩一个游戏,在第 x x x轮,随机生成一个 [ 1 , n ] [1,n] [1,n]的数 i i i,然后会得到 x 2 x^2 x2的贡献。游戏要想玩下去,每次生成的数必须大于等于前面生成的数,也即为当前的最大值。问期望得到的贡献。
思考:首先观察这道题,先明确一点,就当前而言,是无法使用期望的线性性质,也就是说不能简单的认为:
(
x
+
1
)
−
x
=
1
(x+1)-x=1
(x+1)−x=1
当前局面的贡献 =
∑
\sum
∑ (上一个局面的贡献 + 1) * p
理论上应该是:
(
x
+
1
)
2
−
x
2
=
2
∗
x
+
1
(x + 1)^2-x^2=2*x+1
(x+1)2−x2=2∗x+1
当前局面的贡献 =
∑
\sum
∑ (上一局面的贡献 + 2 * x + 1) * p
而这里这个x随着游戏轮数的变换而变化。
然后大胆猜想这里的
x
x
x可以用第一个式子来表示。
在考场上的时候,经典的使用了正向去推期望dp,然后死活处理不对。
考虑用逆向dp,令 d p [ i ] dp[i] dp[i]代表当前最大值为 i i i的用 x x x算贡献(第一个式子)到游戏结束的期望期望, f [ i ] f[i] f[i]代表最大值为 i i i的用 x 2 x^2 x2算贡献(第二个式子)到游戏结束的期望贡献。
有:
d
p
[
i
]
=
∑
j
=
i
j
=
n
(
d
p
[
j
]
+
1
)
∗
p
j
dp[i] = \sum_{j=i}^{j=n} {(dp[j] + 1)*p_j}
dp[i]=∑j=ij=n(dp[j]+1)∗pj
f
[
i
]
=
∑
j
=
1
j
=
n
(
f
[
j
]
+
2
∗
d
p
[
i
]
+
1
)
∗
p
j
f[i] = \sum_{j=1}^{j=n} {(f[j] + 2*dp[i] +1)*p_j}
f[i]=∑j=1j=n(f[j]+2∗dp[i]+1)∗pj
最后再计算一下第一次产生数字的贡献即可。
代码不是我写的,所以就不贴了。
2.牛客多校第一场I
题意:一个排列,两个人轮流取数,每次操作需要满足:
当前取数
a
i
a_i
ai,自己上一次取的数
a
j
a_j
aj,满足
i
>
j
i>j
i>j
当前取数
a
i
a_i
ai,需要大于两个人前面所有的数
求期望的取数次数
思考:显然可以发现,两个人每次取数的时候,存在大小限制和位置限制。如果不是最初的状态,那么两个人其实没有本质区别。而对于期望dp,很重要的一点是逆向考虑。
不妨定义
d
p
[
x
]
[
y
]
dp[x][y]
dp[x][y]当前为此人取数,且他上一次取的数为
x
x
x,另一个人上一次取数为
y
y
y。也就是说,在不考虑人的差别的情况下,上上次的取数为
x
x
x,上一次的取数为
y
y
y。
思考一下可以发现,
d
p
[
x
]
[
y
]
=
∑
i
=
1
i
=
t
o
t
(
d
p
[
y
]
[
b
i
]
+
1
)
∗
i
n
v
[
t
o
t
]
=
1
+
i
n
v
[
t
o
t
]
∗
∑
i
=
1
i
=
t
o
t
d
p
[
y
]
[
b
i
]
dp[x][y] = \sum_{i=1}^{i=tot} {(dp[y][b_i] +1)*inv[tot]} = 1 + inv[tot] * \sum_{i=1}^{i=tot} {dp[y][b_i]}
dp[x][y]=∑i=1i=tot(dp[y][bi]+1)∗inv[tot]=1+inv[tot]∗∑i=1i=totdp[y][bi]
其中
t
o
t
为
tot为
tot为满足:
b
i
>
y
b_i > y
bi>y且
p
o
s
[
b
i
]
>
p
o
s
[
x
]
pos[b_i]>pos[x]
pos[bi]>pos[x]的数的个数。
对于初始状态,第一个人随便取数,第二个人还没取数 d p [ 0 ] [ i ] dp[0][i] dp[0][i],而到达这个局面是有 1 1 1的贡献,所以需要加上。
#include<bits/stdc++.h>
#define For(aa, bb, cc) for(int aa = (bb); aa <= (int)(cc); ++aa)
#define Forr(aa, bb, cc) for(int aa = (bb); aa >= (int)(cc); --aa)
using namespace std;
typedef long long LL;
const int maxn = 5010;
const int mod = 998244353;
int n;
int a[maxn];
int dp[maxn][maxn];
//dp[last_last_val][last_val] now choose the first part
int inv[maxn], pos[maxn], sum[maxn], cnt[maxn];
int qpow(int x, int y){
int res = 1;
while(y){
if(y & 1) res = 1ll * res * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return res;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin.tie(NULL);
cin >> n;
For(i, 1, n){
cin >> a[i], inv[i] = qpow(i, mod - 2);
pos[a[i]] = i;
}
Forr(i, n, 1){ //val i=y
//dp[x][y] = \sigma_{i=1}^{i=tot} {(dp[y][a_i]+1)*inv(tot)}
For(j, 0, n) sum[j] = cnt[j] = 0;
For(j, i + 1, n){ //val j=a_i > y
sum[pos[j]] = dp[i][j]; //j = a_k
cnt[pos[j]] = 1;
}
Forr(j, n - 1, 1){ //pos j
sum[j] = (sum[j] + sum[j + 1]) % mod;
cnt[j] += cnt[j + 1];
}
For(j, 0, i - 1){ //val x=j, pos[a_i] > pos[x]
if(cnt[pos[j] + 1])
dp[j][i] = 1ll * inv[cnt[pos[j] + 1]] * sum[pos[j] + 1] % mod + 1;
}
}
LL ans = 0;
For(i, 1, n) ans = (ans + dp[0][i]) % mod;
cout << (ans * inv[n] % mod + 1) << endl;
return 0;
}