CF1207F Remainder Problem

题意

给定一个初始值全部为 00,长度为 5×1055 \times 10^5 的数列,要支持单点加上某个数,以及查询 y,y+x,y+2x,,y+kxy, y + x, y + 2 \cdot x, \cdots, y + k \cdot x 的数列的对应的值的和,直到 y+kx>ny + k \cdot x > n 时停止。

解法

限时 44 秒,考虑分块或线段树。但是分析后可得,线段树难以维护零散的点的和,而普通分块空间会爆。

那么我们还有一个方法:根号分治。这是一种类似分块的方法,他可以使得平均复杂度在 O(nn)O(n \sqrt{n}) 以内,nn 是数列长度。

考虑设 ansi,jans_{i,j} 表示询问输入的是 i,ji, j 时的答案,那么理论空间复杂度是 O(n2)O(n^2) 的,显然不行。我们注意到询问有一个特征,就是当 y,xy, x 都很小的时候暴力会很慢,而当 y,xy, x 很大时暴力可以接受。暴力的每次复杂度大约是 O(nx)O(\frac{n}{x}) 级别的,所以我们只需要记录 ansi,jans_{i,j},其中 i,jni, j \leq \sqrt{n},那么询问的时候,如果块内有答案,直接 O(1)O(1) 输出,不然暴力的理论最坏复杂度还是 O(n)O(\sqrt{n}) 的,单点修改就没什么难度了,只需要从 11n\sqrt{n} 循环即可,也是 O(n)O(\sqrt{n}) 的。

所以总复杂度最坏是 O(nn)O(n \sqrt{n}) 的,最坏的点约 33 秒,稍微卡一下常数,可以通过。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;

const int l = 708, N = 5e5 + 5;

int n;
long long ans[l + 5][l + 5], a[N];

int main()
{
	scanf("%d", &n);
	while (n--)
	{
		int opt, x, y;
		scanf("%lld%lld%lld", &opt, &x, &y);
		if (opt == 1ll)
		{
			a[x] += y;
			for (int i = 1; i <= l; i++)
			{
				ans[i][x % i] += y;
			}
		}
		else
		{
			if (x <= l) printf("%lld\n", ans[x][y]);
			else
			{
				long long res = 0;
				for (int i = y; i <= N - 5; i += x) res += a[i];
				printf("%lld\n", res);
			}
		}
	}
	return 0;
}
posted @ 2022-04-04 12:51  HappyBobb  阅读(15)  评论(0)    收藏  举报  来源