整除分块

引入问题: ∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^n\lfloor \frac{n}{i}\rfloor i=1nin,其中 n < = 1 0 9 n<=10^9 n<=109

暴力:

for(int i=1;i<=n;i++)
	ans+=n/i;//有手就行

这个的时间复杂度是 n 2 n^2 n2

正解:

i / j i/j i/j最终的答案一定是单调递减的而且可能还有一段是连续的一段数字.

  • 比如 20 20 20除下来的结果就是 20 , 10 , 6 , 5 , 4 , 3 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 20,10,6,5,4,3,2,2,2,2,1,1,1,1,1,1,1,1,1,1 20,10,6,5,4,3,2,2,2,2,1,1,1,1,1,1,1,1,1,1
  • 所以我们每一次算出一段区间 l , r l,r l,r,个数就是 r − l + 1 r-l+1 rl+1然后答案就是 ( r − l + 1 ) ∗ . . (r-l+1)*.. (rl+1)..就行了.
  • 怎么求 l , r l,r l,r呢, r r r就是 i / ( i / j ) i/(i/j) i/(i/j),如果是 n n n的话就是 n / ( n / l ) n/(n/l) n/(n/l),挺好背的,干脆就先背住好了a.jpg(有解释,在后面)
for(int i=1,r;i<=n;i=r+1){
    r=n/(n/i);
    ans+=(r-i+1)*(n/i);
}

这个的时间复杂度是 n \sqrt{n} n


因为过程较多,所以手写好了a.jpg

整除分块_第1张图片

字很丑,格式也没有手打好看,不过只要能看就行啦,反正是学知识(强词夺理)


例题1:求 ∑ i = 1 n ⌊ n a i + b ⌋ \sum_{i=1}^{n}\lfloor \frac{n}{ai+b} \rfloor i=1nai+bn 其中 n , a , b n,a,b n,a,b已知

可以发现我们只是把 i i i变成了 a i + b ai+b ai+b,所以我们按照上面的推导方式再推导一次就行.

整除分块_第2张图片

答案也容易求,就是和上面的一个步骤,求 r r r,用区间个数乘上 k k k就完事儿了.


例题2:求 ∑ i = 1 n ⌊ n i 2 ⌋ \sum_{i=1}^{n}\lfloor \frac{n}{i^2} \rfloor i=1ni2n

整除分块_第3张图片

和上道题差不多,可以用来巩固一下,或者找找解题的感觉.


例题3:求 ∑ n = 1 M a x ∑ i = 1 n n % i \sum_{n=1}^{Max} \sum_{i=1}^{n}n\%i n=1Maxi=1nn%i 其中 1 ≤ n ≤ 1 0 5 1\le n \le 10^{5} 1n105

先把模运算换做除运算 n % i = n − ⌊ n i ⌋ ∗ i n\%i=n-\lfloor \frac{n}i{} \rfloor * i n%i=nini,然后就能出现我们熟悉的式子了,熟悉,但不完全熟悉,假设一个区间 l , r l,r l,r,在这个区间 ⌊ n i ⌋ \lfloor \frac{n}i{} \rfloor in的值都一样, i i i变大 1 1 1,那么整个式子就变小了 ⌊ n i ⌋ \lfloor \frac{n}i{} \rfloor in,这个值是一个常数,所以就是一个等差数列,就可以 O 1 O1 O1的计算出来.

答案会溢出,所以对 998244353 998244353 998244353一个模吧.

#include
#include
#include
#define int long long
#define mod 998244353
using namespace std;
int Max,ans;
signed main(){
	scanf("%lld",&Max);
	for(int n=1;n<=Max;n++){
		for(int i=2,r,l;i<=n;){
			l=i; r=n/(n/i);
			ans=(ans+(r-l+1)*(n-n/i*i)-(n/i)*(r-l+1)*(r-l)/2)%mod;
			i=r+1;
		}
	}
	printf("%lld\n",(ans+mod)%mod);
	return 0;
}

例题4:mmt

题目大意:求 c i = ∑ j = 1 i a ⌊ i j ⌋ b i % j c_i=\sum_{j=1}^{i}a_{\lfloor \frac{i}{j} \rfloor}b_{i\%j} ci=j=1iajibi%j

上面那个题就是这个题的简化版,上个题有的这个题就不解释了.

  • 这个题多了一个 a i a_i ai,那我们再简化一下求 c i = ∑ j = 1 i ⌊ i j ⌋ ∗ b i % j c_i=\sum_{j=1}^{i}{\lfloor \frac{i}{j} \rfloor}*b_{i\%j} ci=j=1ijibi%j其实都是一样的,我们把 i % j i\%j i%j变成 i − ⌊ i j ⌋ ∗ j i-\lfloor \frac{i}{j} \rfloor * j ijij, 也就是 c i = ∑ j = 1 i ⌊ i j ⌋ ∗ b i − ⌊ i j ⌋ ∗ j c_i=\sum_{j=1}^{i}{\lfloor \frac{i}{j} \rfloor}*b_{i-\lfloor \frac{i}{j} \rfloor * j} ci=j=1ijibijij,因为我们是每一个 ⌊ i j ⌋ \lfloor \frac{i}{j} \rfloor ji都是一个区间一个区间的枚举,也就是上一个题的表达式前面再乘上 ⌊ i j ⌋ \lfloor \frac{i}{j} \rfloor ji就行了,所以变成 a ⌊ i j ⌋ a_{\lfloor \frac{i}{j} \rfloor} aji也一样,乘上 a ⌊ i j ⌋ a_{\lfloor \frac{i}{j} \rfloor} aji就行了.
  • 然后也不是 i % j i\%j i%j了,套了一个 b b b在上面,假设原来是 9 + 7 + 5 + 3 + 1 9+7+5+3+1 9+7+5+3+1,现在就是求 b 9 + b 7 + b 5 + b 3 + b 1 b_9+b_7+b_5+b_3+b_1 b9+b7+b5+b3+b1,既然是连续的,那我们为什么不用前缀和呢?相减不就得到了答案了吗
  • 所以我们预处理一个 s [ i ] [ j ] s[i][j] s[i][j]表示以 i i i结束,下标公差为 j j j的前缀和, j j j需要多少合适呢,总不可能开一个 1 0 5 ∗ 1 0 5 10^5*10^5 105105的数组吧,所以 j j j只需要 n \sqrt{n} n 就行了公差超过这个的直接暴力枚举因为公差这么大,所以很快就弄完了.因为方便,我们直接把 j j j设为 200 200 200就行.
  • 流程大概就是枚举一个 l , r l,r l,r然后求 l , r l,r l,r中的 b . . . b_{...} b...之和乘上 a ⌊ i j ⌋ a_{\lfloor \frac{i}{j} \rfloor} aji就行了.
#include
#include
#include
#define maxn 100010
#define mod 123456789
#define int long long
using namespace std;
int n,a[maxn],b[maxn],s[maxn][205];
int calc(int n,int x,int l,int r){
	int ans=0;
	if(n<=200){
		if(l-n/x<0) ans=s[r][n/x];
		else ans=s[r][n/x]-s[l-n/x][n/x];
	}else{
		for(int i=x,temp=n/(n/x);i<=temp;i++) ans=(ans+b[n-i*(n/x)])%mod;
	}
	return (ans+mod)%mod;
}
int solve(int i){
	int ans=0;
	for(int j=1,r;j<=i;){
		r=i/(i/j);
		ans=(ans+a[i/j]*calc(i,j,i-r*(i/j),i-j*(i/j)))%mod;
		j=r+1;
	}
	return ans;
}
signed main(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=0;i<n;i++)  scanf("%lld",&b[i]);
	for(int i=0;i<n;i++){
		for(int j=0;j<=200;j++){
			if(i>=j) s[i][j]=(s[i-j][j]+b[i])%mod;
			else s[i][j]=b[i];
		}
	}
	for(int i=1;i<=n;i++) printf("%lld\n",solve(i));
	return 0;
}

后记:这应该是我人生中第一个与整除分块接触的题,两年前我抱着题解看了一下午也没看懂……这个题noi.ac上好像已经不能评测了,但是这个题我觉得特别棒,如果能过样例,应该能过(我是指大样例),不行的话写对拍也行.


例题5:Bamboo Partition

题意:给定 n n n个数 a 1 → a n a_1\to a_n a1an,求一个最大的 d d d,满足 ∑ i = 1 n d − ( ( a [ i ] − 1 ) % d + 1 ) ≤ k \sum_{i=1}^{n}d-((a[i]-1)\%d+1)\le k i=1nd((a[i]1)%d+1)k.

  • 最开始想的是二分,结果不是,因为这个 d d d和最终的和(记为 a n s ans ans)根本不是线性的.

  • 所以我们现在开始化简这个式子,但是我们要先知道 % \% %运算怎么变成除运算 x % y = x − y ∗ ⌊ x y ⌋ x\%y=x-y*\lfloor \frac{x}{y} \rfloor x%y=xyyx其实这个也挺好理解的, ⌊ x y ⌋ \lfloor \frac{x}{y} \rfloor yx就是去掉余数,在乘上就行了,这个应该挺好理解的.所以原式就变成了
    ∑ i = 1 n d − ( a i − d ∗ ⌊ a i − 1 d ⌋ ) ≤ k d ∗ ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) ≤ k + ∑ i = 1 n a i \sum_{i=1}^{n}d-(a_i-d*\lfloor \frac{a_i-1}{d} \rfloor)\le k\\ d*(n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor)\le k+\sum_{i=1}^{n}a_i i=1nd(aiddai1)kd(n+i=1ndai1)k+i=1nai

  • 到这个地方貌似和整除分块没啥关系….

  • 第一种方法:现在我们需要枚举 d d d,然后 ∑ i = 1 n ⌊ a i − 1 d ⌋ \sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor i=1ndai1其实是只有 m a x   { a i } \sqrt {max\ \{a_i\}} max {ai} 种方法的,参考整除分块,除下来的结果最多只有 n \sqrt n n 个答案的,也就是说我们只需要枚举 1 → n 1\to \sqrt n 1n 就行(这个是其中一种方法,我暂时还没做)

  • 第二种方法:直接把右边 k + ∑ i = 1 n a i k+\sum_{i=1}^{n}a_i k+i=1nai加起来(记作 s u m sum sum),再来看左边,当 d ≤ s u m d\le\sqrt {sum} dsum ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) (n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor) (n+i=1ndai1)是必须大于等于 s u m \sqrt{sum} sum 的,同理 d ≥ s u m d\ge \sqrt{sum} dsum 时, ( n + ∑ i = 1 n ⌊ a i − 1 d ⌋ ) (n+\sum_{i=1}^{n}\lfloor \frac{a_i-1}{d} \rfloor) (n+i=1ndai1)是必须小于等于 s u m \sqrt{sum} sum 的(不然式子不成立).所以枚举 s u m \sqrt{sum} sum 次就行

#include
#include
#include
#define maxn 200
#define int long long
using namespace std;
int n,a[maxn],k,ans,Max;
int calc(int d){
	int Ans=0;
	for(int i=1;i<=n;i++) Ans+=(a[i]-1)/d;
	return Ans;
}
bool check(int d) {return n*d+calc(d)*d<=k;}
signed main(){
	scanf("%lld %lld",&n,&k);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),k+=a[i],Max=max(Max,a[i]);
	for(int i=1;i*i<=Max;i++){
		if(check(i)) ans=max(ans,i);
		if(check(k/i)) ans=max(ans,k/i);
	}
	printf("%lld\n",ans);
	return 0;
}

行啦,讲完了,算是入门了,以后有题在弄上来,听说莫比乌斯反演里面用的多.

你可能感兴趣的:(学习笔记,线性代数,算法,数学,c++)