小 h 学步

数学 序列dp

首先我们做个差,每个维度的每个坐标减一下上一个坐标,这样子就变成了相对坐标(相对于上一步)。接下来可以发现 和值域很小,只有 ,因此先打个暴力试试水。

表示目前走了 步,坐标为 ,那么下一次就可以选择第 组中的任意一个坐标加上。时间复杂度 ,但是由于 因此直接炸裂。

仔细思考,可以发现每个维度是可以拆开来进行计算的。假设 吗,第 步选择了 ,那么它会对答案做出 的贡献,拆成三分分别相加。这样动态规划就只用处理一维的问题 ,加上玄学优化可以直接通过。

不过还可以进一步思考。既然每一维分开计算了那么假设当前这一维选择了 ,那么它会给答案增加 的贡献,拆开得到 。对于一个 ,其它地方有 种选项,因此它首先会给答案贡献 。然后对于后面一坨,它会给答案贡献 ,但是由于每一组可以任选(即 可能是第 组中的任意一个),而每一组每个维度的和为 ,因此后面那一大坨其实都是 。答案就是 ,其中 是第 组第 个的 维坐标。

时间复杂度 。其实 还是太小了,应该把 或者 扩大到 ,不然太有“疑似暴力”的诱导性了。

#include <iostream>
#include <vector>
 
using namespace std;
using LL = long long;
 
const LL kMaxN = 105, kMod = 998244353;
 
LL n, d, ans;
vector<LL> a[kMaxN][kMaxN];
 
LL pow(LL a, LL b) {
  LL res = 1;
  for (; b; b /= 2) {
    b % 2 && (res = res * a % kMod);
    a = a * a % kMod;
  }
  return res;
}
 
int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin >> n >> d;
  for (LL i = 1; i <= n; i++) {
    for (LL j = 1; j < n; j++) {
      a[i][j].resize(d);
      for (LL &k : a[i][j]) {
        cin >> k;
      }
    }
  }
  for (LL i = 1; i <= n; i++) {
    a[i][n] = a[i][n - 1];
    for (LL &j : a[i][n]) {
      j = -j;
    }
    for (LL j = n - 1; j >= 2; j--) {
      for (LL k = 0; k < d; k++) {
        a[i][j][k] -= a[i][j - 1][k];
      }
    }
  }
  for (LL i = 0; i < d; i++) {
    for (LL j = 1; j <= n; j++) {
      for (LL k = 1; k <= n; k++) {
        ans = (ans + a[j][k][i] * a[j][k][i] % kMod * pow(n, n - 1) % kMod) % kMod;
      }
    }
  }
  cout << ans << '\n';
  return 0;
}

小球进洞

概率与期望 概率dp 树形dp

我打的暴力。爆搜 层挖洞,最后一个 统计。对于一个非叶子节点,如果它的子树边上有 个洞,又有 个叶子节点,那么它的球就有 种方式掉进洞里面。由于每个球是独立的,方案数就是它们的乘积。把所有掉洞的方案数加起来再除以挖洞方案数就是答案。时间复杂度 ,成功获得 分。

快速 kmp

不到啊,我没打暴力。

宿舍派对

不到啊,我没打暴力。

其实可以 bfs 暴力。枚举终点,这样子前进就是有方向的了。时间复杂度 ,可以获得 分。

总结

总分:

第一题还是可以的,找到了公式薄纱了一部分纯暴力的。不过第二题没有想出来 的暴力 dp,说明概率和期望还得再练。最后一题脑子抽风了没有拿到应该拿的十分,有点可惜。至于第三题,实在太难了,还要练。