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;
}
分类:
题目总结
, 题目总结 / 2024. 10~12月
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现