Educational Codeforces Round 71 (Rated for Div. 2)
F. Remainder Problem
题目链接
F. Remainder Problem
题意:
给你一个由 500000 500000 500000 个整数(编号从 1 1 1 到 500000 500000 500000 )组成的数组 a a a 。最初 a a a 中的所有元素都为零。
您必须对这个数组进行两种类型的查询:
- 1 1 1 x x x y y y - a x a_x ax 增加 y y y ;
- 2 2 2 x x x y y y - 计算 ∑ i ∈ R ( x , y ) a i \sum\limits_{i \in R(x, y)} a_i i∈R(x,y)∑ai ,其中 R ( x , y ) R(x, y) R(x,y) 是所有从 1 1 1 到 500000 500000 500000 有模 x x x 同余 y y y 的整数的集合。
你能处理所有的查询吗?
思路:
很好的一道根号分治题,类似的还有这道 F. Sum of Progression 。题解的F题
操作 1 1 1 好说,看操作 2 2 2 如何实现。发现其实就是求 ∑ a i ∗ x + y \sum a_{i*x+y} ∑ai∗x+y 形式的数,对这种形式,大佬说:
当步长 x x x 很大的时候,我们其实枚举不了几次就会到边界 n n n ,因此 x x x 很大的时候直接枚举。而当 x x x 比较小的时候,我们可以提前处理出结果。这里把起始位置为 j j j ,步长为 i i i 的这一系列位置上的数的和设为 s [ i ] [ j ] s[i][j] s[i][j],这样操作 1 1 1 修改的时候只需要枚举步长 i i i,给所有 s [ i ] [ x % i ] s[i][x\%i] s[i][x%i] 加 y y y 就可以维护住了。
而这个分界点就取为 n \sqrt n n,当 x ≥ n x\ge\sqrt n x≥n 时,就用第一种枚举求和的方式来算。否则就直接查询 s [ i ] [ j ] s[i][j] s[i][j]。这样每次暴力枚举的次数不会超过 n \sqrt n n,而每次修改的时候维护 s s s 数组时枚举的步长也只需要枚举 n \sqrt n n 次。总的时间复杂度就是枚举的复杂度加上维护 s s s 数组的复杂度,总的复杂度不会超过 q n q\sqrt n qn。
为什么分界点取为 n \sqrt n n?证明只需要用均值不等式就可以了。最坏情况显然是要么操作 1 1 1 修改,要么操作 2 2 2 使用枚举方式查询答案。如果分界点选为 t t t 最优,假设操作 1 1 1 有 x x x 次,那么操作 2 2 2 要 q − x q-x q−x 次。总的操作次数是 x ∗ t + ( q − x ) ∗ n t x*t+(q-x)*\frac nt x∗t+(q−x)∗tn,因为算最坏的情况,因此需要 t = n t t=\frac nt t=tn,否则就会按着用时最多的操作 a l l i n all\ in all in,因此就有了 t = n t=\sqrt n t=n,总的时间复杂度就是 x ∗ n + ( q − x ) ∗ n = q ∗ n x*\sqrt n+(q-x)*\sqrt n=q*\sqrt n x∗n+(q−x)∗n=q∗n。
code:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int n=5e5;
const int sqn=710;int t,x,y;
ll a[n+5],s[sqn+5][sqn+5];//初值 步长int main(){int _;cin>>_;while(_--){scanf("%d%d%d",&t,&x,&y);if(t&1){a[x]+=y;for(int i=1;i<sqn;i++)s[i][x%i]+=y;}else {if(x>=sqn){ll ans=0;for(int i=y;i<=n;i+=x)ans+=a[i];printf("%lld\n",ans);}else {printf("%lld\n",s[x][y]);}}} return 0;
}