數論——數論分塊

RainPPR發表於2024-05-23

數論——數論分塊

數論分塊可以快速的求解形如,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \sum_{i=1}^nf(i)g\left(\floor{n\over i}\right) \]

的求和式。

不考慮 \(f,g\) 的複雜度,數論分塊的複雜度是 \(\mathcal O(\sqrt n)\) 的。

其思想很好玩哦(

下取整的性質

容易發現,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \floor{n\over i} \]

下文所說個數均為規模,表示大概。

\(i\in[1,\sqrt n)\) 的時候,一共只有 \(\sqrt n\) 個式子。

\(i\in[\sqrt n,n]\) 的時候,一共只有 \(\sqrt n\) 種不同的結果。

因此,我們可以對此分塊,使總複雜度降為 \(\mathcal O(\sqrt n)\)

數論分塊的結論

對於常熟 \(n\),使得

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \floor{n\over i}=\floor{n\over j} \]

成立的最大 \(i\le j\le n\)\(j\) 為,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \floor{n\over\floor{n/i}} \]

於是,我們可以根據左端點,推斷出右端點,進而推斷出下一個塊的左端點。

這就是數論分塊的本質。

證明不會。

過程

考慮最簡單的形式,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \sum_{i=1}^nf(i)\floor{n\over i} \]

顯然我們要處理 \(f\) 的字首和,記為 \(s\)

由於後面的東西是塊狀分佈的,因此數論分塊。

每次以一塊,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} [l,r]=\left[l,\floor{n\over\floor{n/i}}\right] \]

隨後對於下一塊,更新區間左端點,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} l\gets r+1=\floor{n\over\floor{n/i}}+1 \]

參考程式碼,

int solev(int n) {
	int l = 1, r, ans = 0;
	while (l <= n) {
		r = n / (n / l);
		ans += (n / l) * (s(r) - s(l - 1));
		// ans += (n / l) * calc(l, r);
		l = r + 1;
	}
	return ans;
}

例題一

P3935 Calculating

第一步推式子,題中給出函式 \(f(x)\) 表示 \(x\) 的因數個數,因此答案,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \sum_{x=l}^r\sum_{i=1}^x\floor{x\over i} \]

左側用差分,因此要求,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} f(x)=\sum_{i=1}^x\floor{x\over i} \]

即標準形式的數論分塊,程式碼,

ll solev(ll n) {
	ll l = 1, r, ans = 0;
	while (l <= n) {
		r = n / (n / l);
		ans = (ans + (n / l) * (r - l + 1) % mod) % mod;
		l = r + 1;
	}
	return ans;
}

例題二

P2424 約數和

已經給出了式子,整理,即

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \sum_{x=l}^r\sum_{i=1}^xi\floor{x\over i} \]

左側用差分,因此要求,

\[\def\floor#1{\left\lfloor{#1}\right\rfloor} \sum_{i=1}^xi\floor{x\over i} \]

即標準形式的數論分塊,程式碼,

ll calc(int l, int r) {
	return ((ll)l + r) * (r - l + 1) / 2;
}

ll solev(int x) {
	int l = 1, r;
	ll ans = 0;
	while (l <= x) {
		r = x / (x / l);
		ans += calc(l, r) * (x / l);
		l = r + 1;
	}
	return ans;
}

例題三

P2261 [CQOI2007] 餘數求和

有點技巧,因為容易發現列舉上界和被除數不統一。

迴歸數論分塊的本質,容易發現其實只需要知道塊的左右端點就可以了。

於是也容易得出,我們對右端點取 \(n\)\(\min\),注意除數不為零即可。

程式碼,

ll calc(int l, int r) {
	return ((ll)l + r) * (r - l + 1) / 2;
}

ll solev(int n, int x) {
	int l = 1, r;
	ll ans = 0;
	while (l <= n) {
		r = n;
		if (x / l) r = min(r, x / (x / l));
		ans += calc(l, r) * (x / l);
		l = r + 1;
	}
	return ans;
}