题意:很简单了,不再赘述。
做法:
首先我们注意到,因为 \(b_i\ge 2\),所以我们无脑全局操作 \(\log V\) 次之后一定可以满足条件。
同时注意到我们是对区间中最小的 \(b\) 以他为除数,所以说我们很自然可以去建出一个 \(b\) 的小根笛卡尔树。
发现对于一个笛卡尔树的子树,我们肯定是直接全部一起操作若干次,两个子树间再分别操作是最优的。
因为操作次数非常少,所以我们考虑直接 dp,\(dp_{u,j}\) 代表 \(u\) 的子树内,操作 \(j\) 次可以得到的答案。转移时,我们先枚举子树中的操作,显然是一个背包的转移方式,记得对 \(a_u\) 取 \(\max\)。合并完之后我们考虑对当前点 \(u\) 操作,那么就直接从前往后令 \(dp_{u,j}\) 对 \(\lceil\frac{dp_{u,j-1}}{b_u}\rceil\) 取 \(\min\) 即可。
复杂度 \(O(n\log^2n)\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 2e5 + 5, mx = 64;
int n, a[maxn], b[maxn], dp[maxn][70], st[maxn][21], lg[maxn];
int getmax(int x, int y) {return (b[x] < b[y] ? x : y);
}
void prepare() {lg[0] = -1;for (int i = 1; i <= n; i++)lg[i] = lg[i >> 1] + 1;for (int j = 1; (1 << j) <= n; j++)for (int i = 1; i + (1 << j) - 1 <= n; i++)st[i][j] = getmax(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);
}
int query(int l, int r) {int k = lg[r - l + 1];return getmax(st[l][k], st[r - (1 << k) + 1][k]);
}
int solve(int l, int r) {if(l > r)return 0;if(l == r) {dp[l][0] = a[l];for (int i = 1; i <= mx; i++)dp[l][i] = (dp[l][i - 1] + b[l] - 1) / b[l];return l;} int mid = query(l, r);int ls = solve(l, mid - 1), rs = solve(mid + 1, r);
// cout << dp[ls][0] << " " << dp[rs][0] << " " << l << " " << r << " " << mid << endl;for (int i = 0; i <= mx; i++)for (int j = 0; j <= mx; j++) {if(i + j <= mx)dp[mid][i + j] = min(dp[mid][i + j], max(dp[ls][i], max(dp[rs][j], a[mid])));}for (int i = 1; i <= mx; i++)dp[mid][i] = min(dp[mid][i], dp[mid][i - 1]);for (int i = 1; i <= mx; i++)dp[mid][i] = min(dp[mid][i], (dp[mid][i - 1] + b[mid] - 1) / b[mid]);return mid;
}
void solve() {cin >> n;for (int i = 1; i <= n; i++)cin >> a[i];for (int i = 1; i <= n; i++)cin >> b[i], st[i][0] = i;prepare();for (int i = 1; i <= n; i++)for (int j = 0; j <= mx; j++)dp[i][j] = 9e18;int rt = solve(1, n);for (int i = 0; i <= mx; i++) {if(dp[rt][i] <= 1) {cout << i << endl;return ;}}
}
signed main() {int T;cin >> T;while(T--)solve();return 0;
}
