[CF2048F] Kevin and Math Class 题解

注意到这里有个区间的 \(b_i\) 最小。我们考虑每个 \(b_i\) 作为最小的时候各操作几次。显然每个 \(b_i\) 的【操作区间】更长是不劣的。于是这些个 \(b_i\) 是可以打成笛卡尔树,进行 DP。

想到这一点,DP 便是不难的了。
定义 \(f_{i, j}\) 为以 \(i\) 为根的子树内,最优操作后最大值为 \(j\) 的最少操作次数。但是状态数很多,而答案的值域很少(每次都至少折半,顶多 \(60\) 多次)。有个很典的套路是将状态的一维与答案对调。
于是定义新的 \(f_{i, j}\) 为以 \(i\) 为根的子树内,操作 \(j\) 次后得到的最优的最大值。答案统计就是最小的 \(j\),满足 \(f_{root, j} = 1\)

DP 过程就是一个背包合并。DP 的过程总先合并儿子,再将本身统计进去,可以巧妙地解决 \(b_i\) 要算进整个区间的小事情。
注意 \(\min,\max\) 的细节。
时间复杂度 \(O(n \log ^2 V)\)

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define all(v) v.begin(), v.end()
#define int long long
using namespace std;

//#define filename "xxx" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
#define multi_cases 1

namespace Traveller {
	const int N = 2e5+2;
	
	int n, a[N], b[N];
	int root, l[N], r[N];
	stack<int> st;
	
	int f[N][62];
	void solve(int u) {
		if(u == 0) return;
		if(l[u] == 0 && r[u] == 0) {
			f[u][0] = a[u];
			for(int i = 1; i < 62; ++i) f[u][i] = f[u][i-1] / b[u];
			return;
		}
		
		solve(l[u]), solve(r[u]);
		for(int i = 0; i < 62; ++i)
			for(int j = 0; j <= i; ++j)
				f[u][i] = min(f[u][i], max(a[u], max(f[l[u]][j], f[r[u]][i-j])));
		for(int i = 1; i < 62; ++i)
			f[u][i] = min(f[u][i], f[u][i-1] / b[u]);
	}
	
	void main() {
		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]);
		
		for(int i = 1; i <= n; ++i) l[i] = r[i] = 0;
		
		while(!st.empty()) st.pop();
		b[root = 0] = Linf;
		for(int i = 1; i <= n; ++i) {
			while(!st.empty() && b[i] < b[st.top()]) l[i] = st.top(), st.pop();
			if(!st.empty()) r[st.top()] = i; 
			st.push(i);
			if(b[i] < b[root]) root = i;
		}
		
		for(int i = 1; i <= n; ++i)
			for(int j = 0; j < 62; ++j) f[i][j] = Linf;
		solve(root);
		for(int i = 0; i < 62; ++i)
			if(f[root][i] == 0) {
				printf("%lld\n", i);
				return;
			}
	}
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	while(_--) Traveller::main();
	return 0;
}

闲话:一开始写了很__的做法,结果 T 飞。后来抄题解抄了这种做法。时间复杂度相同,由于(笛卡尔树的某种性质?数据太弱?)神秘原因,快了许多。950 多 ms 直接飘过。正解应该是要用一下单调性,合并的时候上点手法消掉一只 \(\log\)

posted @ 2025-01-17 20:22  Water_M  阅读(39)  评论(0)    收藏  举报