1. 程式人生 > 其它 >【數論】數論分塊

【數論】數論分塊

【數論】數論分塊

求解圖中紅點和綠點的總數之和。

求$$\sum_{i=1}^{n}k \space mod\space i$$?

如果縱軸的x一個點一個點進行移動的話,將會非常緩慢。

前置知識

\(k\%i=k-[\frac{k}i]\times i\)

於是有\(\sum_{i=1}^{n}k \space mod\space i=\sum_{i=1}^{n}k-[\frac{k}i]\times i=n\times k-\sum_{i=1}^{n}i\times[\frac{k}{i}]\)

所以我們重點是要去求取\(\sum_{i=1}^{n}i\times[\frac{k}{i}]\)

的值。

若在一段區間內\([\frac{k}{i}]\)保持不變,可以將其看作是一個常數,而i是線性變化的,於是我們就可以通過等差數列的求和公式求出這一段區間的值。

而數論的分塊(暫時的理解)是快速通過這些區間的左端點來求出區間的右端點,從而降低時間損耗。

就如圖上的紅點變成綠點,而不需要把每一個點都計算出答案。

x 1 2 3 4 5 6 7 8 9 10 11
\([\frac{11}{x}]\) 11 5 3 2 2 1 1 1 1 1 1

對於任意一個i,求一個最大的j使得\([\frac{n}{i}]=[\frac{n}{j}]\)

且此時\(j=[\frac{n}{[\frac{n}{i}]}]\)

證明

\(k=[\frac{n}{i}]<=\frac{n}{i}\) 。(k為商,即矩形所對應的高度)

  • 則有\(j=[\frac{n}{k}]>=[\frac{n}{\frac{n}{i}}]=[i]=i\),所以j>=i(證明j在這個值下大於任意一個i)

  • 前提引入,\([\frac{n}{i}]=[\frac{n}{j}]\)

    進而在替換後有,\([\frac{n}{j}]=k\),所以有\(k<=\frac{n}{j}<k+1\),進一步有\(\frac{n}{k+1}<j<=\frac{n}{k}\)

    j為整數,進而有\([\frac{n}{k}]=j_{max}\)

複雜度證明

通過打表不難發現,\(\lfloor\frac ni\rfloor\)最多有$2\cdot\sqrt n \(種取值,所以,整除分塊的複雜度為\)O(\sqrt n)$。

證明:

\(i\le\sqrt n\),顯然最多有\(\sqrt n\)種取值(i的取值都只有\(\sqrt n\)種)。

\(i>\sqrt n\),則\(\lfloor\frac ni\rfloor<\sqrt n\),取值亦不超過\(\sqrt n\)種。

模板題

P2261 [CQOI2007]餘數求和

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define MAX 1000005
#define MOD 1000000007
using namespace std;
const int N = 3E5+5,M = 6E5+10;
ll n,k;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>k;
	ll ans = n * k;
	for(ll l=1,r;l<=n;l=r+1)
	{
		if(k/l!=0) r = min(k/(k/l),n);
		else r = n;
		ans -= (r-l+1)*(l+r)*(k/l)/2;
	}
	cout<<ans;
    return 0;
}