P10037 「FAOI-R2」梨花开 (C) 题解

比较有意思的 dp 题。

思路#

考虑答案至少为 p 的条件是什么。

  1. 我们显然会把根节点的热量平均分配到深度前 p 小的点上,那么要求 minj=1ni=1jvi+kp0
  2. 其次,在热量到达前 k 小的点前,i=1vi 不可以小于 0

只要满足了这两个条件,我们的答案就至少为 p,假设这样的方案数为 fp

那么答案为:

fp

如何求解。

dpi,j 为第 i 步后 k=1ivk=j 的方案数。

在转移时可以判断一下是否合法即可。

转移可以用前缀和优化。

复杂度:O(nmt2)

可以获得 20 分。

发现我们的限制只和 depikp 有关。

因为操作只有 t 次,所以要求 depit

而根据整除分块,kp 只有根号项,所以记忆化一下,可以优化复杂度。

复杂度:O((t+n)mt2)

视常数可以获得 5060 分。

容易发现我们满足前缀和不小于 0 的 dp 都是一样的操作。

那么我们可以进行预处理。

fi,j 表示在 i 轮后满足前缀和一直不小于 0,且 k=1ivk=j 的方案数。

转移同样枚举下一位然后前缀和优化。

同样,我们想要仿造的求出 gi,j 表示 i 轮后满足前缀和一直不小于 j 的方案数。

这是我们直接 dp 的后半段。

这个怎么求呢?

这类问题好像有一个比较常见的 trick。

就是在维护前缀和的最小值时,我们正着不好做,但是反过来是很好求的。

考虑反过来怎么求前缀和的最小值:

summin(0,sum+v)

这个式子我们就可以 dp 了。

求出了 gi,j 以后,就可以轻松搭配 fi,j 求出这个询问的答案了。

时间复杂度:O((k+t)mt+mt2)

Code#

#include <bits/stdc++.h>
using namespace std;

#define eb(...) emplace_back(__VA_ARGS__)
#define fro(i, x, y) for (int i = (x); i <= (y); i++)
#define pre(i, x, y) for (int i = (x); i >= (y); i--)

const int N = 1e6 + 10;
const int mod = 998244353;

int n, m, t, k, ans, d[N], R[N], sm[N];
int dp1[510][30000];
int dp2[510][30000];
vector<int> to[N];

inline void dfs(int x, int f) {
  d[x] = d[f] + 1;
  for (auto i : to[x])
    if (i != f) dfs(i, x);
}
inline void add(int&x, int y) {
  x = (x + y >= mod ? x + y - mod : x + y);
}

signed main() {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> m >> t >> k;
  fro(i, 1, n - 1) {
    int u, v;
    cin >> u >> v;
    to[u].eb(v);
    to[v].eb(u);
  }
  dfs(1, 0);
  sort(d + 1, d + n + 1);
  dp1[0][R[0] = 0] = dp2[0][R[0] = 0] = 1;
  fro(i, 1, t) {
    fro(j, 0, R[i - 1]) {
      add(dp1[i][max(0, j - m)], dp1[i - 1][j]);
      add(dp1[i][j + m + 1], mod - dp1[i - 1][j]);
      add(dp2[i][max(0, j - m)], dp2[i - 1][j]);
      add(dp2[i][j + m + 1], mod - dp2[i - 1][j]);
    }
    R[i] = R[i - 1] + m;
    fro(j, 1, R[i] + 1) add(dp1[i][j], dp1[i][j - 1]);
    fro(j, 1, R[i] + 1) add(dp2[i][j], dp2[i][j - 1]);
    fro(j, 0, R[i - 1]) if (m - j > 0) add(dp2[i][0], 1ll * (m - j) * dp2[i - 1][j] % mod);
  }
  fro(i, 1, t) fro(j, 1, R[i]) add(dp2[i][j], dp2[i][j - 1]);
  fro(i, 1, n) {
    int v = k / i, p = min(t + 2, d[i]);
    if (i == 1) {
      sm[i] = dp2[t][min(v, R[t])];
    } else if (v != k / (i - 1) || p != min(t + 2, d[i - 1])) {
      int l = p - 2;
      int r = t - p + 2;
      fro(j, 0, R[l]) add(sm[i], 1ll * dp1[l][j] * dp2[r][min(v + j, R[r])] % mod);
    } else sm[i] = sm[i - 1];
    add(ans, sm[i]);
  }
  cout << ans << "\n";
  return 0;
}

作者:JiaY19

出处:https://www.cnblogs.com/JiaY19/p/18267403

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   JiaY19  阅读(12)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示