整除分块入门小记

command_block

2019-04-01 16:37:10

Personal

如果你是从[莫比乌斯反演与数论函数](https://www.luogu.org/blog/command-block/mu-bi-wu-si-fan-yan-ji-ji-ying-yong)这里来,那就对了。 否则建议你去那里看看。 ## 一维整除分块 先看[题目](https://www.luogu.org/problemnew/show/P2261)。 求$\sum\limits_i^nk\%i$ 别看到$\%$一脸懵逼,老司机会把$k\%i$变成$k-i\left\lfloor\dfrac{k}{i}\right\rfloor$。 然后就变为$\sum\limits_i^n(k-i\left\lfloor\dfrac{k}{i}\right\rfloor)=kn-\sum\limits_i^ni\left\lfloor\dfrac{k}{i}\right\rfloor$ 问题就在于后面那个$\sum\limits_i^ni\left\lfloor\dfrac{k}{i}\right\rfloor$怎么求。 **弱化版:** 求$\sum\limits_i^n\left\lfloor\dfrac{n}{i}\right\rfloor$ 具体怎么做呢?观察到$n/i$只有$O(\sqrt{n})$(约两倍$\sqrt{n}$)种取值: ```cpp ################################################## 25/1=25 * ######################## 25/2=12 * ################ 25/3=8 * ############ 25/4=6 * ########## 25/5=5 * ######## 25/6=4 * ###### 25/7=3 * ###### 25/8=3 #### 25/9=2 * #### 25/10=2 #### 25/11=2 #### 25/12=2 ## 25/13=1 * ## 25/14=1 ## 25/15=1 ## 25/16=1 ## 25/17=1 ## 25/18=1 ## 25/19=1 ## 25/20=1 ## 25/21=1 ## 25/22=1 ## 25/23=1 ## 25/24=1 ## 25/25=1 ``` 得到$25/i$只有9种取值,也就是9块。 ```cpp ################################################## 25 ---------- ######################## 12 ---------- ################ 8 ---------- ############ 6 ---------- ########## 5 ---------- ######## 4 ---------- ###### 3*2 ###### ---------- #### 2*4 ... #### ---------- ## 1*12 ... ## ---------- ``` 如果能算出每块的“长度”与“高度”,就可以算出“面积总和”,也就是**总值**。 假如说我们求出了某一块,即$i=[l,r]$时,$\left\lfloor\dfrac{n}{i}\right\rfloor$都相同。 对于每一组$[l,r]$对应着一个长方形,长为$r-l+1$宽为$n/l$。 于是答案加上$(r-l+1)(n/l)$。 考虑我们知道了$[l,r]$这一块之后,如何求出下一块。 设$N/i'$与$N/i$相等,则$i'$的最大值为$N/(N/i)$。 - 这个没有沉下去之前,都有整除和非整除的概念 $\color{blue}\text{证明:}$ **必要性**:$\left\lfloor\dfrac{N}{i'}\right\rfloor$与$\left\lfloor\dfrac{N}{i}\right\rfloor$相等,也就是$\left\lfloor\dfrac{N}{i'}\right\rfloor≥\left\lfloor\dfrac{N}{i}\right\rfloor$(废话) ,得到$\dfrac{N}{i'}≥\left\lfloor\dfrac{N}{i}\right\rfloor$ 两边取倒数,不等号变向$\dfrac{i'}{N}≤1/(\left\lfloor\dfrac{N}{i}\right\rfloor)$ 两边同乘$N$,得到$i'≤N/(\left\lfloor\dfrac{N}{i}\right\rfloor)$ 也就是$i'≤\left\lfloor\dfrac{N}{\left\lfloor\dfrac{N}{i}\right\rfloor}\right\rfloor$ **充分性**:令$i'=\left\lfloor\dfrac{N}{\left\lfloor\dfrac{N}{i}\right\rfloor}\right\rfloor$得到$\left\lfloor\dfrac{N}{i'}\right\rfloor=\left\lfloor\dfrac{N}{\left\lfloor\dfrac{N}{\left\lfloor\dfrac{N}{i}\right\rfloor}\right\rfloor}\right\rfloor=\left\lfloor\dfrac{N}{i}\right\rfloor$ 明显可见$i'$的最大值为$N/(N/i)$。 也就是说我们知道了一个区间的左端点之后,我们能知道右端点,然后左端点++。 写出代码来是这样子的。 ```cpp #include<iostream> #define mod 998244353 using namespace std; long long n; long long calc(long long n) { long long ans=0,l=1,r; for (;l<=n;){ r=n/(n/l);//得到右端点 ans=(ans+(r-l+1)*(n/l))%mod; l=r+1;//前往下一块 }return ans; } int main() { cin>>n; cout<<calc(n)<<endl; } ``` 重复一遍,这个程序求的是$\sum\limits_i^n\left\lfloor\dfrac{n}{i}\right\rfloor$ 有一道[简单题](https://www.luogu.org/problemnew/show/P1403)可以用上述代码AC。 (不过建议大家还是要推一下,这是个经典结论。) 然而,这只是弱化版,我们真正的目的是~~蓝题~~求$\sum\limits_i^ni\left\lfloor\dfrac{k}{i}\right\rfloor$。 这里有一个统一的技巧。 假设我们要求$\sum\limits_i^nf(i)\left\lfloor\dfrac{k}{i}\right\rfloor$,($f(x)是某数论函数$) 我们看$\left\lfloor\dfrac{k}{i}\right\rfloor$值相同的某一块$[l,r]$ 得到$f(l)*\left\lfloor\dfrac{k}{l}\right\rfloor+f(l+1)*\left\lfloor\dfrac{k}{l}\right\rfloor+...+f(r)*\left\lfloor\dfrac{k}{l}\right\rfloor$ $=(f(l)+f(l+1)+...+f(r))\left\lfloor\dfrac{k}{l}\right\rfloor$ 这里我们明显看到:对于一块,$ANS+=(k/l)*\text{函数对应区间和}$ 我们维护一个前缀和即可。 **附:** 前面的$\sum\limits_i^n\left\lfloor\dfrac{n}{i}\right\rfloor$其实就是$\sum\limits_i^nI(i)\left\lfloor\dfrac{n}{i}\right\rfloor$,$I(i)[l,r]$的和就是(r-l+1),和上面推导的结果相同。 那么你也看出来了,$\sum\limits_i^ni\left\lfloor\dfrac{k}{i}\right\rfloor$就是$\sum\limits_i^nid(i)\left\lfloor\dfrac{k}{i}\right\rfloor$,那么对$id(i)$做一个区间和,不就是等差数列求和嘛。 **Code** **(有细节需要注意!)** ```cpp #include<iostream> using namespace std; long long n,k,ans; inline long long idsum(long long l,long long r) {return (r-l+1)*(l+r)>>1;} long long calc(long long n) { long long ans=0,l=1,r; for (;l<=n&&k/l;){ //这里要注意,假如k<n的话,k/l有可能出现0,直接跳过 r=min(k/(k/l),n); //这里要注意min一下n ans+=(k/l)*idsum(l,r); l=r+1; }return ans; } int main() { cin>>n>>k; cout<<n*k-calc(n); return 0; } ``` ## 二维整除分块 [P2260 [清华集训2012]模积和](https://www.luogu.org/problemnew/show/P2260) 好像是上面那道题的升级版? 题意:求$\sum\limits_{i=1}^n\sum\limits_{j=1}^m[i≠j](n\%i)(m\%j)$,对某个数取模。 默认$n<m$,下面一定要注意大小关系! $=\sum\limits_{i=1}^n\sum\limits_{j=1}^m(n-\lfloor n/i\rfloor*i)(m-\lfloor m/j\rfloor*j)[i≠j]$ $=\sum\limits_{i=1}^n\sum\limits_{j=1}^m(n-\lfloor n/i\rfloor*i)(m-\lfloor m/j\rfloor*j)-\sum\limits_{i=1}^n(n-\lfloor n/i\rfloor*i)(m-\lfloor m/i\rfloor*i)$ 前半部分: $=\sum\limits_{i=1}^n(n-\lfloor n/i\rfloor*i)\sum\limits_{j=1}^m(m-\lfloor m/j\rfloor*j)$ 设$F(n)=\sum\limits_{i=1}^n(n-\lfloor n/i\rfloor*i)$ 则前半部分$=F(n)F(m)$ $F(n)=\sum\limits_{i=1}^n(n-\lfloor n/i\rfloor*i)$ $=n^2-\sum\limits_{i=1}^n\lfloor n/i\rfloor*i$ 设$G(n,k)=\sum\limits_{i=1}^n\lfloor k/i\rfloor*i$,上式=$n^2-G(n,n)$ $G(n,k)$直接如上题整除分块即可。 后半部分: $\sum\limits_{i=1}^n(n-\lfloor n/i\rfloor*i)(m-\lfloor m/i\rfloor*i)$ $=\sum\limits_{i=1}^n(nm-m\lfloor n/i\rfloor*i-n\lfloor m/i\rfloor*i+\lfloor n/i\rfloor\lfloor m/i\rfloor*i^2)$ $=n^2m-mG(n,n)-nG(n,m)+\sum\limits_{i=1}^n\lfloor n/i\rfloor\lfloor m/i\rfloor*i^2$ What?$\sum\limits_{i=1}^n\lfloor n/i\rfloor\lfloor m/i\rfloor*i^2$这个东西好像并不是很好办? > $\lfloor n/i\rfloor\lfloor m/i\rfloor$也有类似$\lfloor n/i\rfloor$的规律,也会形成块状。 > 令n=10,m=15,得到。 ```cpp ********************************************************************************(10/1)*(15/1)=150 ***********************************(10/2)*(15/2)=35 ***************(10/3)*(15/3)=15 ******(10/ 4)*(15/ 4)=6 ******(10/ 5)*(15/ 5)=6 **(10/ 6)*(15/ 6)=2 **(10/ 7)*(15/ 7)=2 *(10/ 8)*(15/ 8)=1 *(10/ 9)*(15/ 9)=1 *(10/10)*(15/10)=1 ``` > 可以看到也形成块状了,但是具体怎么分块? > 其实就是两个块状楼梯叠合在一起(形象理解!~~MC中掉落沙子~~)。 > 所以拐角的个数还是$O(\sqrt{n})$,(约4倍$\sqrt{n}$) > 我们找到了一个左端点, > 那么右端点=($(n/i)$楼梯的右端点,与$(m/i)$楼梯的右端点中,最近的一个)。 > 因为上文:$N/i'$与$N/i$相等,则$i'$的最大值为$N/(N/i)$。 > 所以右端点=$min(n/(n/i),m/(m/i))$; 还有一个$i^2$怎么办?,我们通过小学奥数(代码里面的S函数)可以得到$i^2$的区间和,其余同上。 综上,$ANS=F(n)F(m)-n^2m+mG(n,n)+nG(n,m)-\sum\limits_{i=1}^n\lfloor n/i\rfloor\lfloor m/i\rfloor*i^2$ 复杂度$O(\sqrt{n})$ **Code:** ```cpp #include<algorithm> #include<cstdio> #define mod 19940417 #define inv6 3323403 #define inv2 9970209 using namespace std; int n,m; int G(int n,int lim) { int l=1,r,ans=0; for (;l<=lim;l=r+1){ r=min(n/(n/l),lim); ans=(ans+1ll*(n/l)*(r+l)%mod*(r-l+1)%mod*inv2)%mod; }return ans; } int F(int n) {return (1ll*n*n-G(n,n))%mod;} int S(int n) {return 1ll*n*(n+1)%mod*(n+n+1)%mod*inv6%mod;} int main() { scanf("%d%d",&n,&m); if (n>m)swap(n,m); int ans=1ll*F(n)*F(m)%mod; ans=(ans+1ll*n*G(m,n)+1ll*m*G(n,n))%mod; ans=(ans-1ll*n*n%mod*m)%mod; int l=1,r; for (;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans=(ans-1ll*(n/l)*(m/l)%mod*(S(r)-S(l-1)))%mod; }printf("%d",(ans+mod)%mod); return 0; } ``` 恭喜你,又学习到了**整除分块**!