2024初秋集训——提高组 #21
B. 网格游走
题目描述
你在一个 \(r\times c\) 的网格图的 \((1,1)\) 处。每个格子上都有一个箭头和计时器,一开始,箭头等概率地指向右/下方,计时器上等概率地显示 \([0,p]\) 中的一个实数。当计时器归零时,箭头指向的方向会翻转,即向右变成向下,向下变成向右,并且计时器会重置为 \(p\)。
你在一个格子上时,可以:
- 如果当前格子箭头向右,且右边有格子,那么可以走到右边的格子。
- 如果当前格子箭头向下,且下边有格子,那么可以走到下边的格子。
- 等待计时器归零。
你的移动不需要时间,只有等待需要。你只能观察到当前格子的箭头和计时器状态。假设你按照最优方式操作,求到达 \((r,c)\) 的所需期望时间。
思路
令 \(dp_{i,j}\) 表示从 \((i,j)\) 开始到达 \((r,c)\) 的期望时间。
有两种转移:
-
若 \(i<r,j<c\)。我们令 \(a=\min(dp_{i+1,j},dp_{i,j+1}),b=\max(dp_{i+1,j},dp_{i,j+1})\)。有 \(\frac{1}{2}\) 的概率一开始箭头指向 \(a\),但还有 \(\frac{1}{2}\) 的概率指向 \(b\),假设当前计时器为 \(x\),并令 \(v=\min(b-a,p)\):
-
若 \(x\le v\),那么我们肯定会选择等待并走 \(a\),期望为 \((a+\frac{v}{2})\cdot \frac{v}{p}\),\(a+\frac{v}{2}\) 为这种情况的期望时间,\(\frac{v}{p}\) 是这种情况的概率。
-
若 \(x>v\),那么我们会直接走 \(b\),期望为 \(b\cdot \frac{p-v}{p}\)。
-
所以我们有转移 \(dp_{i,j}=\frac{a}{2}+\frac{1}{2}((a+\frac{v}{2})\cdot \frac{v}{p}+b\cdot \frac{p-v}{p})\)。
-
-
若 \(i=r\),那么显然有转移 \(dp_{i,j}=dp_{i,j+1}+\frac{p}{4}\),这里 \(\frac{p}{4}\) 是 \(\frac{p}{2}\) 的期望时间乘以 \(\frac{1}{2}\) 的概率指向下方。
-
若 \(j=c\),那么显然有转移 \(dp_{i,j}=dp_{i,j+1}+\frac{p}{4}\)。
时空复杂度均为 \(O(rc)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ld = long double;
const int MAXN = 1005;
const ld INF = 1e16;
int n, m, p;
ld dp[MAXN][MAXN];
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> p;
for(int i = n; i >= 1; --i) {
for(int j = m; j >= 1; --j) {
if(i != n || j != m) {
if(i == n) {
dp[i][j] = dp[i][j + 1] + 1.0l * p / 4.0l;
}else if(j == m) {
dp[i][j] = dp[i + 1][j] + 1.0l * p / 4.0l;
}else {
ld a = dp[i + 1][j], b = dp[i][j + 1];
if(a > b) {
swap(a, b);
}
ld x = min(0.0l + p, b - a);
dp[i][j] = a / 2.0l + ((a + x / 2.0l) * x / p + 1.0l * b * (p - x) / p) / 2.0l;
}
}
}
}
cout << fixed << setprecision(114) << dp[1][1];
return 0;
}
D. NAC 子序列
题目描述
给定一个长度为 \(N\) 的仅由大写英文字母和 ?
构成的字符串 \(S\),你要将 \(S\) 中每个字符 ?
替换成任意大写英文字母。求有多少种替换方式使得 \(S\) 中恰好有 \(k\) 个子序列 NAC
。
思路
首先,如果 \((\frac{N}{3})^3<k\),那么直接输出无解。因为我们可以前中后分别放 \(\frac{N}{3}\) 个 N
,A
,C
。
令 \(dp_{i,a,b,c}\) 表示考虑前 \(i\) 个,有 \(a\) 个 N
,\(b\) 个 NA
,\(c\) 个 NAC
是否可行。由于只用记录是否可行,所以使用 bitset
优化转移即可。
时空复杂度均为 \(O(\frac{N^7}{w\cdot V})\)。其中 \(w=32\),\(V=2^2\cdot 3^3\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 41;
int n, m;
string s, ans;
bitset<MAXN * MAXN * MAXN / 27> dp[MAXN][MAXN][MAXN * MAXN / 4];
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m >> s;
s = ' ' + s;
if((n / 3 + (n % 3 > 0)) * (n / 3 + (n % 3 > 1)) * (n / 3) < m) {
cout << -1;
return 0;
}
dp[0][0][0][0] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= n; ++j) {
for(int k = 0; k <= n * n / 4; ++k) {
if((s[i] == 'N' || s[i] == '?') && j) {
dp[i][j][k] |= dp[i - 1][j - 1][k];
}
if((s[i] == 'A' || s[i] == '?') && k >= j) {
dp[i][j][k] |= dp[i - 1][j][k - j];
}
if(s[i] == 'C' || s[i] == '?') {
dp[i][j][k] |= (dp[i - 1][j][k] << k);
}
if(s[i] != 'N' && s[i] != 'A' && s[i] != 'C') {
dp[i][j][k] |= dp[i - 1][j][k];
}
}
}
}
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= n * n / 4; ++j) {
if(dp[n][i][j][m]) {
int k = m;
for(int id = n; id >= 1; --id) {
if((s[id] == 'N' || s[id] == '?') && i && dp[id - 1][i - 1][j][k]) {
ans += 'N', i--;
}else if((s[id] == 'A' || s[id] == '?') && j >= i && dp[id - 1][i][j - i][k]) {
ans += 'A', j -= i;
}else if((s[id] == 'C' || s[id] == '?') && k >= j && dp[id - 1][i][j][k - j]) {
ans += 'C', k -= j;
}else {
ans += (s[id] == '?' ? 'B' : s[id]);
}
}
reverse(ans.begin(), ans.end());
cout << ans;
return 0;
}
}
}
cout << -1;
return 0;
}