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}\)NAC

\(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;
}
posted @ 2024-10-01 10:25  Yaosicheng124  阅读(4)  评论(0编辑  收藏  举报