整除分块
[CQOI2007]余数求和
题目大意:求 ∑ni=1k mod i∑i=1nk mod i 的值。
等等……这题就学了三天C++的都会吧?
1≤n,k≤1091≤n,k≤109。(一口老血喷到屏幕上)
O(n)O(n) 行不通了,考虑别的做法。
我们来看一下 ⌊xi⌋⌊xi⌋ 的值。
x=9x=9:(不包括0,只有4种取值?)
i |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
x/i | 9 | 4 | 3 | 2 | 1 | 1 | 1 | 1 | 1 |
0 |
x=12x=12:(不包括0,只有6种取值?)
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
x/i | 12 | 6 | 4 | 3 | 2 | 2 | 1 | 1 | 1 | 1 | 1 |
1 |
貌似 ⌊xi⌋⌊xi⌋ 取值数不是很多?
我们来估算一下 ⌊xi⌋⌊xi⌋ 的不同取值个数:
当 1≤i≤⌊x−−√⌋1≤i≤⌊x⌋ 时,ii 都只有 ⌊x−−√⌋⌊x⌋ 个,不同的取值数肯定不会更多。
当 ⌊x−−√⌋≤i≤x⌊x⌋≤i≤x 时,1≤⌊xi⌋≤⌊x−−√⌋1≤⌊xi⌋≤⌊x⌋,不同的取值数肯定 ≤⌊x−−√⌋≤⌊x⌋ 个。
综上,不同取值数是 x−−√x 级别的。
然后我们可以发现相同的数是连续的一段。那么我们可以通过这个特点把 ⌊xi⌋⌊xi⌋ 分成几段,每一段的数相等,那么这一段的和就是长度 ××这个相同的数。
因为不同取值只有 x−−√x 个,所以这样加速后的时间复杂度是 O(x−−√)O(x),比 O(x)O(x) 快了不少。这就是整除分块。
回到原题。
求 ∑ni=1k mod i∑i=1nk mod i 的值。
这个……看着和整除分块没什么大关系的样子?
我们看这个 modmod 真碍眼,把它拆开。
k mod i=k−i×⌊ki⌋k mod i=k−i×⌊ki⌋
那么就有:
∑ni=1k mod i ∑i=1nk mod i
=∑ni=1k−i×⌊ki⌋=∑i=1nk−i×⌊ki⌋
=nk−∑ni=1i×⌊ki⌋=nk−∑i=1ni×⌊ki⌋
后面这个式子貌似可以整除分块了……怎么算呢?
我们考虑 [l,r][l,r] 这段区间的求和,其中 ⌊ki⌋=x:i∈[l,r]⌊ki⌋=x:i∈[l,r]。
∑ri=li×⌊ki⌋ ∑i=lri×⌊ki⌋
=∑ri=li×x=∑i=lri×x
=x∑ri=li=x∑i=lri
=x(l+r)(r−l+1)2=x(l+r)(r−l+1)2
这样就不是很难了。
话说讲了这么久也没讲怎么枚举一段相同区间的左端点和右端点。
我们这样扫描:
一开始 l=1l=1 显而易见。
求出对应的 rr。
这个区间求完了,下一个 ll 应该是下一个还没扫过的位置,即 l=r+1l=r+1。
一直重复直到 ll 到了上界,也就是扫完了。
怎么求对应的 rr 呢?
既然 ⌊kl⌋=⌊kr⌋⌊kl⌋=⌊kr⌋,且 rr 是右端点(最大)
那么 r=kklr=kkl。(当然可能要跟枚举上界取一个min,视情况而定)
整除分块模板大概如下:
1 for(int l=1,r;l<=n;l=r+1){ 2 r=n/(n/l); 3 //do something... 4 }
那么这题代码实现就不难了。需要注意本题有不少坑点,详见代码。(没错,代码并没有你想象的那么长!)
时间复杂度貌似是 O(min(k,n)−−−−−−−−√)O(min(k,n)),空间复杂度 O(1)
#include<iostream> #include<cmath> using namespace std; typedef long long ll; //long long是需要的 ll n,k,ans; int main(){ cin>>n>>k; ans=n*k; for(ll l=1,r;l<=min(n,k);l=r+1){ //与上界取min! r=min(k/(k/l),n); //与上界取min! ans-=(k/l)*(l+r)*(r-l+1)/2; } cout<<ans<<endl; }