CF1603C Extreme Extension (数论+dp)
upd 2024.11.14
数论+dp
首先要思考的一定是怎么操作操作次数会最小。然后就会发现一种贪心策略,即让每次分裂出来的最前面的数尽可能大。
求它的所有非空子段的极端值之和不好直接求,但它等价于求每个位置可能的操作次数(可重)之和。(答案的转化)
由上面可以发现,一个数的操作次数只与上一个数操作后的最前面的数有关,也就是说与前面的数无关,所以对最后的这个数的操作次数只有系数的影响。
那我们就考虑 dp 求解每个位置的贡献。维护什么贡献?
”如果知道上一个位置操作后的最前面的数,也就知道现在位置的操作次数“(充分不必要),那我们只需要知道有多少个子段能够得到这个操作次数就好了。
所以设状态
表示有多少以 为开头的子段,使得 拆分完之后最小值为 。转移 。 复杂度
。怎么优化?需要注意到下取整的性质,每个 最多只有 种取值。所以每个阶段最多有根号级别的转移量,那我们只需要枚举这些数就行了。
拿到一题有神秘操作的题目,先考虑把神秘操作搞清楚
对于一个子段,最末尾的数一定不能动,考虑从后往前贪心,当出现
此时根据
考虑转移,朴素的做法,枚举所有
发现对于每一个
所以考虑优化 dp,首先滚动数组,空间复杂度为
考虑按位计算答案,对于每个
复杂度降到
#include <bits/stdc++.h>
typedef long long i64;
const int mod = 998244353;
void Solve() {
int n;
std::cin >> n;
std::vector<int> a(n + 1);
int mx = 0;
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
mx = std::max(mx, a[i]);
}
std::vector<std::vector<int>> dp(2, std::vector<int> (mx + 1));
std::vector<int> v[2];
int op = 1;
i64 ans = 0;
for(int i = n; i >= 1; i--) {
dp[op][a[i]] = 1;
v[op].push_back(a[i]);
int lst = a[i];
for(auto x : v[op ^ 1]) {
int cnt = (a[i] + x - 1) / x;
int num = a[i] / cnt;
ans = (ans + (i64)i * (cnt - 1) % mod * dp[op ^ 1][x]) % mod;
dp[op][num] += dp[op ^ 1][x];
if(num != lst) {
v[op].push_back(num), lst = num;
}
}
for(auto x : v[op ^ 1]) {
dp[op ^ 1][x] = 0;
}
v[op ^ 1].clear();
op ^= 1;
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t;
std::cin >> t;
while(t--) Solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具