等差数列加

等差数列加

根号分治

等差数列加 - 题目 - Daimayuan Online Judge

设一个阈值 \(M\)

给等差数列的位置上加 k

  1. 若公差 d >= M, 则暴力加,每次操作复杂度最高为 \(O(\frac nM)\)
  2. 若公差 d < M, 则可以打标记记录下公差为 \(d\), 第一个加的位置为 \(y\) ,给这个等差数列打上一个 +k 的标记

查询

  1. 本来的这个数经过暴力修改后为 \(a[x]\)
  2. 再加上打标记的,枚举公差 \(d\), 若满足 \(x\mod d=y\), 则可以加,即 \(+tag[d][x\mod d]\), 一共有 \(M\) 个 d 要枚举,复杂度为 \(O(M)\)

每次操作复杂度为 \(O(\frac nM\;or\;M)\), \(M=\sqrt n\) 时取到最小值,总复杂度为 \(O(q*\sqrt n)\)

调试技巧:

  1. 可根据分治的两种情况的常数,选择 M, 不一定严格是 \(\sqrt n\)
  2. 对拍的话不用再打个暴力,改几个 M 不同的值,如果不一样就一定错了

其他根号算法

  1. 图染色
  2. 莫队
  3. 整除分块
  4. 和为 n 的一些数(取值为 1~n),最多有 \(\sqrt {2n}\) 种取值(等差数列前 n 项和可推导出)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;
#define endl "\n"

typedef long long ll;
typedef pair<int, int> PII;

const int N = 2e5 + 10, M = 500;
int n, q;
ll tag[M][M];
ll a[N];
int main()
{
	scanf("%d%d", &n, &q);
	while(q--)
	{
		int op;
		scanf("%d", &op);
		if (op == 1)
		{
			int x, y, d;
			scanf("%d%d%d", &x, &y, &d);
			if (x >= M)
			{
				for (int i = y; i <= n; i += x)
					a[i] += d;
			}
			else
				tag[x][y] += d;
		}
		else
		{
			int x;
			scanf("%d", &x);
			ll ans = a[x];
			for (int i = 1; i < M; i++)
				ans += tag[i][x % i];
			cout << ans << endl;
		}
	}
    return 0;
}
posted @ 2022-06-11 11:43  hzy0227  阅读(71)  评论(0编辑  收藏  举报