CF2048F. Kevin and Math Class

原题链接

题面

给定序列 \(a\), \(b\), 每次操作可以任选 \(1 \le l \le r \le n\), 令 \(x = \min_{i = l}^r b_i\), 使得 \(a_i = \frac{a_i}{x},\ l \le i \le r\).

算法

笛卡尔树, 树上 dp.

思路

观察到 \(b_i \ge 2\), 所以最多只会进行 \(\log_2n\) 次除法, 也就是不超过 63 次操作.

而对于每一个 \(b_i\), 选取一段最长的区间 \([l,\ r]\) 使得 \(\min_{j = l}^r b_j = b_i\) 进行操作一定不劣.
于是我们可以对每一个 \(b_i\) 预处理出其能够到达的最长区间, 而一颗小根笛卡尔树正好满足这样的条件, 故我们考虑建树后再在树上进行 dp.

\(f_{u, i}\) 表示以 \(u\) 为根的子树, 最多进行 \(i\) 次操作, 所得到的 \(a_i\) 最大值最小能为多少.
那么答案即为 \(f_{1, i} = 1\) 的最小的 \(i\).
考虑怎么转移.

  • 如果该点没有儿子, 那么就有 \(f_{u,i} = a_u\), 再对自己进行转移 \(f_{u,i} = \min(\lceil \frac{f_{u, i - 1}}{b_u} \rceil, f_{u, i})\).
  • 如果该点只有一个儿子, 同理, 有 \(f_{u, i} = \min(a_u, f_{son, i})\), 再对自己进行转移 \(f_{u,i} = \min(\lceil \frac{f_{u, i - 1}}{b_u} \rceil, f_{u, i})\).
  • 如果该点有两个儿子, 我们就像背包一样合并左右儿子, 在同上进行同样的操作即可.

因为 \(\lceil \frac{a}{b} \rceil = 1 + \lfloor \frac{a - 1}{b} \rfloor\), 所以我将 \(a_i\) 先减了一.

#include "cstdio"
#include "iostream"
#include "cstring"

using namespace std;

#define int long long

constexpr int N = 2e5 + 1, INF = 1e18;

int n, a[N], b[N], f[N][64];
int ls[N], rs[N], st[N], top = 0;

void init() {
    top = 0;
    scanf("%lld", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lld", a + i), --a[i];
    for (int i = 1; i <= n; ++i)
        scanf("%lld", b + i), ls[i] = rs[i] = 0;
}

void dfs(int u) {
    if (!ls[u] and !rs[u]) {
        f[u][0] = a[u];
        for (int i = 1; i <= 63; ++i)
            f[u][i] = f[u][i - 1] / b[u];
        return;
    }
    if (!ls[u] or !rs[u]) {
        int v = (ls[u] > 0 ? ls[u] : rs[u]);
        dfs(v);
        for (int i = 0; i <= 63; ++i)
            f[u][i] = max(a[u], f[v][i]);
        for (int i = 1; i <= 63; ++i)
            f[u][i] = min(f[u][i], f[u][i - 1] / b[u]);
        return;
    }
    dfs(ls[u]), dfs(rs[u]);
    for (int i = 0; i <= 63; ++i)
        for (int j = 0; j <= i; ++j)
            f[u][i] = min(f[u][i], max(a[u], max(f[ls[u]][j], f[rs[u]][i - j])));
    for (int i = 1; i <= 63; ++i)
        f[u][i] = min(f[u][i], f[u][i - 1] / b[u]);
}

void calculate() {
    st[++top] = 1;
    for (int i = 2; i <= n; ++i) {
        while (top and b[st[top]] > b[i])
            ls[i] = st[top--];
        if (top)
            rs[st[top]] = i;
        st[++top] = i;
    }
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= 63; ++j)
            f[i][j] = INF;
    dfs(st[1]);
    for (int i = 0; i <= 63; ++i)
        if (!f[st[1]][i])
            return printf("%lld\n", i), void();
}

void solve() {
    init();
    calculate();
}

signed main() {
    int t;
    scanf("%lld", &t);
    while (t--)
        solve();
    return 0;
}
posted @   Steven1013  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示