數論——數論分塊
數論分塊可以快速的求解形如,
\[\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;
}