[小专题]除法分块

备份本地笔记




题目大概还是: [CQOI2007]余数之和-除法分块

题意:计算$sum_{i=1}^n k mod i,n,kleq 10^9$

关于除法分块

内容:对于形如$sum_{i=1}^n f(lfloor frac{m}{i} floor)$ 的函数进行求和,如果能$O(1)$地对$f(x)$求和,那么我们有$O(sqrt m)$复杂度的方法对整个式子求和

- 证明:
  - 对于$i<sqrt m$的部分,显然至多只有$sqrt m$个可能的$lfloor frac{m}{i} floor$
  - 对于$igeq sqrt m$,有$lfloor frac{m}{i} floor leq sqrt m$,因此至多也只有$sqrt m$个可能取值
  - 因此整个$lfloor frac{m}{i} floor$至多$2sqrt m$种不同的值
  - 所以总的复杂度还是$O(sqrt m)$
- 另一方面,我们也能证明复杂度下界$Omega (sqrt m)$:
  - 对于任意两个$l,rleq sqrt m$同时要$l eq r,(l,rin Z^+)$
  - 若他们作为分母对应的值$frac{m}{l}$与$frac{m}{r}$**相同**,不妨设$l<r$,则有$frac{m}{l}-frac{m}{r}<1$,即$frac{m(r-l)}{lr}<1$
  - 得到$r-l<frac{lr}{m}<1$,矛盾
  - 因此对于$sqrt m$以内的每个分母$x$,对应的$lfloorfrac{m}{x} floor$都是唯一的
- 到此我们就证明了这个算法的复杂度是$Theta (sqrt m)$的
- 那么问题来了,如何找到每一个连续区间?
- 换句话说,对于每个给定的左端点$i$,如何快速求出它的右端点?

for(int i=1,pos;i<=n;i=pos+1)
{
    if(m/i)pos=min(n,m/(m/i));//k>i,防止分母为0
    else pos=n;
    //pos 即右端点
}



- 右端点$r=lfloor frac{m}{lfloor frac{m}{i} floor} floor$ 。可以这么想:$lfloor frac{m}{i} floor$是这一个**“块”**里对应的值$t$,如果把区间看成连续的,$frac{m}{t}$再取整得到的正是满足这个块里值也为$t$的右端点,下取整则得到离散的区间对应的位置。

做法

回到这题,根据定义展开$sum_{i=1}^n k mod i=nk-sum_{i=1}^n lfloor frac{k}{i} floor imes i$

后面的和式直接用除法分块求和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n,k,ans;
inline ll sum(ll x)
{
    return x*(x+1)/2;
}

int main()
{
    scanf("%lld%lld",&n,&k);
    ans=n*k;
    for(register ll i=1,pos;i<=n;i=pos+1)
    {
        if(k/i)pos=min(n,k/(k/i));
        else pos=n;
        ans-=(k/i)*(sum(pos)-sum(i-1));
    }
    printf("%lld",ans);
    return 0;
}



时间复杂度$O(sqrt k)$

原文地址:https://www.cnblogs.com/yoshinow2001/p/13829413.html