备份本地笔记
题目大概还是: [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)$