根号分治
以一道题来说明何谓根号分治。
P3396 哈希冲突#
- 如果对于每个操作进行暴力求解,即从
开始不断累加 。单次询问时间复杂度: ,总时间复杂度 。 - 可以预先处理好每个询问的结果,即用上一步的方法计算后将结果保存下来,用
表示模数为 时, 池内的数字综合,询问时直接输出。预处理时间复杂度: ,单次询问时间复杂度: ,总时间复杂度: 。 - 为什么前两种方法这么慢呢?因为询问拖慢了速度,即从
开始累加 ,最多有可能累加 次。 - 那么如何减少累加次数呢?
- 可以把我们的第1, 2步综合起来。分情况讨论一下:
5. 1. 如果询问的模数 ,那么可以使用第一种方法暴力枚举,最多可能累加的次数为: 次,所以单次询问最坏情况的时间复杂度为 ,如果进行 次操作,则最坏时间复杂度为 。
5. 2. 如果询问的模数 ,那么可以使用第二种方法,先将所有结果存下来,共 种模数,共 个数需要统计,预处理时间复杂度: ,询问时间复杂度: ,预处理 + 询问最坏时间复杂度为 。 - 因为
和 差不多,所以时间复杂度为 。 - 修改第
的值为 时,先对每一个模数下的 进行处理,即 ,时间复杂度: ,再对 进行处理,即 即可。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 150010, S = 400;
int n, m, s;
int a[N];
int f[S][S];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
s = sqrt(n);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= s; i++) {
for (int j = 1; j <= n; j++) {
f[i][j % i] += a[j];
}
}
char opt;
int x, y;
for (int i = 1; i <= m; i++) {
cin >> opt >> x >> y;
if (opt == 'A') {
if (x <= s) cout << f[x][y] << '\n';
else {
int ans = 0;
for (int j = y; j <= n; j += x) ans += a[j];
cout << ans << '\n';
}
}
else {
for (int j = 1; j <= s; j++) f[j][x % j] = f[j][x % j] - a[x] + y;
a[x] = y;
}
}
return 0;
}
CF103D Time to Raid Cowavans#
与上一题类似,对询问的左端点进行排序,处理到哪里就把前面的数字全删了。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
using i64 = long long;
const int N = 300010, S = 600;
int n, m, s;
int a[N];
i64 res[N];
i64 f[S][S];
struct query {
int x, y, id;
}q[N];
bool cmp(const query a, const query b) {
return a.x < b.x;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
s = sqrt(n);
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> q[i].x >> q[i].y;
q[i].id = i;
}
sort(q + 1, q + m + 1, cmp);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= s; j++) {
f[j][i % j] += a[i];
}
}
int last_position = 1;
for (int i = 1; i <= m; i++) {
int x = q[i].x, y = q[i].y, id = q[i].id;
if (y <= s) {
while (last_position < x) {
for (int j = 1; j <= s; j++) {
f[j][last_position % j] -= a[last_position];
}
last_position++;
}
res[id] = f[y][x % y];
}
else {
i64 ans = 0;
for (int j = x; j <= n; j += y) ans += a[j];
res[id] = ans;
}
}
for (int i = 1; i <= m; i++) cout << res[i] << '\n';
return 0;
}
CF1654E Arithmetic Operations#
具体可见我写的题解。
以下内容与题解大致相同:
题目让我们求改变数字的最少次数,那我们转化一下,
求可以保留最多的数字个数
我们先考虑两种暴力方法。
第一种暴力方法:
大体思路:因为要保留的最多,那么我们肯定要在众多等差数列中找能对应数字最多的那一个并保留下来。
首先,我们要知道一个概念。
对于这道题,那么我们可以暴力枚举公差
对于同一个公差
如果有
那我们可以想到,用桶记录计算出来的值
如果
这种方法的时间复杂度为
第二种暴力方法:
考虑动态规划,设
我们可以枚举上一个数字
那这个序列的公差是多少呢?
这样考虑,中间有
如果除不尽怎么办呢,那么这就说明
那
这个等会儿讲。
那么为了平衡这两种暴力算法,我们可以这样办:
取输入的数列
我们只使用第一种方法枚举
我们使用第二种方法枚举
下面探讨第二种方法的时间复杂度,
首先回归到前面的问题,来探讨
到哪里好解决,就是
而开始的地方,是
首先,因为公差
首先假设
所以,第二种方法的时间复杂度为
那么这个题的时间复杂度就为
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <unordered_map>
using namespace std;
const int N = 100010;
int n;
int a[N], maxx, sqrtmaxx;
int u[(int)(N + N * sqrt(N))]; // 第一种暴力方法的桶
unordered_map<int, int> f[N]; // 第二种暴力方法的动态规划数组。
int max_keep() {
int ans = 0;
for (int d = 0; d <= sqrtmaxx; d++) { // 第一种暴力方法,枚举公差 D
for (int i = 1; i <= n; i++) {
ans = max(ans, ++u[a[i] + (n - i) * d]);
}
for (int i = 1; i <= n; i++) {
u[a[i] + (n - i) * d]--;
}
}
for (int i = 1; i <= n; i++) { // 第二种暴力方法,动态规划
for (int j = max(1, i - sqrtmaxx); j < i; j++) {// j只用从 i - sqrt(m) 开始枚举
if ((a[i] - a[j]) % (i - j) == 0) {
int x = (a[i] - a[j]) / (i - j);
if (x <= sqrtmaxx) continue;
f[i][x] = max(f[i][x], f[j][x] + 1);
ans = max(f[i][x] + 1, ans);
}
}
}
for (int i = 1; i <= n; i++) f[i].clear(); // 清空数组
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], maxx = max(maxx, a[i]);
sqrtmaxx = sqrt(maxx);
int ans1 = 0, ans2 = 0;
ans1 = max_keep();
reverse(a + 1, a + n + 1); // 应对公差为负数的情况
ans2 = max_keep();
cout << n - max(ans1, ans2) << '\n';
return 0;
}
CF1580C Train Maintenance#
我们以
设当前在进行第
对于
对于
时间复杂度:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 200010, V = 450;
int n, m, s;
int st[N];
int f[N];
int last[V][V];
int x[N], y[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
s = sqrt(m);
for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
int opt, a;
int sum = 0;
for (int i = 1; i <= m; i++) {
cin >> opt >> a;
if (opt == 1) {
if (x[a] + y[a] > s) {
for (int j = i + x[a]; j <= m; j += x[a] + y[a]) {
f[j] += 1;
if (j + y[a] <= m) f[j + y[a]] -= 1;
}
}
else {
int b = i + x[a], e = i + x[a] + y[a] - 1;
for (int j = b; j <= e; j++) {
last[x[a] + y[a]][j % (x[a] + y[a])]++;
}
}
st[a] = i;
}
else {
if (x[a] + y[a] > s) {
for (int j = st[a] + x[a]; j <= m; j += x[a] + y[a]) {
f[j] -= 1;
if (j < i) sum--;
if (j + y[a] <= m) {
f[j + y[a]] += 1;
if (j + y[a] < i) sum++;
}
}
}
else {
for (int j = st[a] + x[a]; j < st[a] + x[a] + y[a]; j++) {
last[x[a] + y[a]][j % (x[a] + y[a])]--;
}
}
}
sum += f[i];
int res = sum;
for (int j = 1; j <= s; j++) res += last[j][i % j];
cout << res << '\n';
}
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工具