(根号分治)nfls #1982 倍数点更新 题解
题意
给定一个长度为 nnn 的序列 xxx,编号从 111 开始,所有元素初值为 000,接下来 qqq 次操作,操作有两种 op=1/2,每次给出 a,b:
op=1,更新下标为 aaa 的倍数的点,即xa×i←xa×i+bx_{a\times i}\leftarrow x_{a\times i}+bxa×i←xa×i+b,a×i≤na\times i\le na×i≤n;op=2,询问 ∑i=lrxi\displaystyle\sum_{i=l}^r x_ii=l∑rxi,保证 l≤rl\le rl≤r。
1≤n,m≤1051\le n,m\le 10^51≤n,m≤105,op=1 时 b∈[1,108]b\in[1,10^8]b∈[1,108]。
思路
我们发现修改比较离散,似乎没法线段树,求区间和前缀和也不好搞,考虑在树状数组上 add 和 query 前缀和。
看到倍数,可以分类去跳:将 aaa 分为大因数(大于 bSize=nbSize=\sqrt{n}bSize=n)和小因数。大因数直接 O(n)O(\sqrt{n})O(n) 枚举下标即可。
那么小倍数呢?发现我们容易计算 [l,r][l,r][l,r] 中 xxx 的因数有 r/x-(l-1)/x 个,我们可以枚举 bSizebSizebSize 内的因数——考虑一个 tagxtag_xtagx 表示,下标为 xxx 的倍数全体加 tagxtag_xtagx。而这样打 tag 只用记录小因数,枚举因数也只需要 bSizebSizebSize 次。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls u<<1
#define rs u<<1|1
const ll N=1e5+9;
ll n,Q;
ll bSize,tag[N];
struct BT
{ll T[N];ll lowbit(ll x){return x&(-x);}void add(ll x,ll k){for(int i=x;i<N;i+=lowbit(i))T[i]+=k;}ll query(ll x){ll ret=0;for(int i=x;i>=1;i-=lowbit(i))ret+=T[i];return ret;}
}B;
int main()
{scanf("%lld%lld",&n,&Q);bSize=sqrt(n);while(Q--){ll op,a,b;scanf("%lld%lld%lld",&op,&a,&b);if(op==1){if(a<=bSize)tag[a]+=b;else for(int i=a;i<=n;i+=a)B.add(i,b);}else {ll ret=0;for(int i=1;i<=bSize;i++)ret+=tag[i]*(b/i-(a-1)/i);ret+=B.query(b)-B.query(a-1);printf("%lld\n",ret);}}return 0;
}
