【SP26073】DIVCNT1 - Counting Divisors 题解
題目描述
定義 \(d(n)\) 為 \(n\) 的正因數的個數,比如 \(d(2) = 2, d(6) = 4\)。
令 $ S_1(n) = \sum_{i=1}^n d(i) $
給定 \(n\),求 \(S_1(n)\)。
輸入格式
第一行包含一個正整數 \(T\) (\(T \leq 10^5\)),表示數據組數。
接下來的 \(T\) 行,每行包含一個正整數 \(n\) (\(n < 2^{63}\))。
輸出格式
對于每個 \(n\),輸出一行一個整數,表示 \(S_1(n)\) 的值。
題解
顯然 $ S_1(n) = \sum_{i=1}^n \left \lfloor \frac ni \right \rfloor $
整除分塊可以做到\(O \left( \sqrt n \right)\),這是要死的復雜度。
但實際上,這玩意是有個優化套路的:
顯然有$ \sum_{i=1}^n \left \lfloor \frac ni \right \rfloor = 2 \sum_{i=1}^{\lfloor \sqrt n \rfloor} \left \lfloor \frac ni \right \rfloor - \left \lfloor \sqrt n \right \rfloor^2 $
那么只需求 \(\sum\limits_{i=1}^{\lfloor \sqrt n \rfloor} \left \lfloor \dfrac ni \right \rfloor\) 的值
它顯然等于 \(f(x) = \dfrac n x\)、直線 \(y = \sqrt n\)、\(x\) 軸和 \(y\) 軸所圍成的區域 \(R\) 中整點 (格點) 的個數 (含邊界,\(x\) 軸除外)。我們把它變成了一個區間數點問題。
但實際上這個函數是個凸函數,因此區域 \(R\) 的補集 \(R'\) 為一個凸集,我們很顯然的想到用凸包去擬合,然后Pick定理隨便搞一搞。
怎么找凸包呢?我們用SBT大力求斜率,然后弄個單調棧去搞。
記 \(B = \left \lfloor \sqrt n \right \rfloor\),我們從點 \(P_0 \left( \left \lfloor \dfrac nB \right \rfloor, B+1 \right)\) 開始,一步一步尋找凸包上所有的點。
(步驟一)我們在棧中加入兩個分數 (為斜率的絕對值) \(\dfrac 01\) 和 \(\dfrac 11\),代表向量 \((1, 0)\) 和 \((1, 1)\),由于 \(f' \left( \sqrt n \right) = -1\),因此這部分所有斜率都在 \(-1 \sim 0\) 之間。 此外,這個棧需要滿足自頂向上是單調遞增的。
(步驟二)接下來,我們取出棧頂向量,將 \(P_0\) 持續與這個向量 (關于 \(x\) 軸的對稱,下略) 相加,直到這個點 \(P_k\) 不在區域 \(R'\) 中 (即在區域 \(R\) 中)。由于這些分數是在 Stern-Brocot 樹中產生的,因此一定是既約分數 (即分子與分母互素)。
因此每加一步,我們可以計算出這個橫條的面積:設向量為 \((u, v)\),上一個點 (\(P_{k-1}\)) 的橫坐標為 \(x\),則面積為 \(x v + \dfrac 12 (v + 1) (u - 1)\)。
(步驟三)接著我們要對棧進行調整。由于函數的斜率的絕對值單調遞減,因此棧中的分數也需要單調遞減。
故我們需要把值過大的分數彈出棧外,直到棧頂和它下面的元素與 \(P_k\) 相加后,前者在 \(R'\) 外,后者在 \(R'\) 內。把這兩個向量記作 \(l\) 和 \(r\)。
(步驟四)然后我們在SBT上二分(不是說大力求嘛).
記 \(l = \dfrac {y_l} {x_l}, r = \dfrac {y_r} {x_r}\) (\(l > r\)),則 \(m = \dfrac {y_m} {x_m} = \dfrac {y_l + y_r} {x_l + x_r}\)。令 \(M = P_k + m\) (即 \(P_k\) 按照向量 \(m\) 平移后的點),如果 \(M\) 在 \(R'\) 中,則將 \(r\) 壓入棧后令 \(r \gets m\),繼續二分;否則,分以下兩種情況討論(二輪判斷):
一:如果 \(\left| f' \left( x_k + x_m \right) \right| \leq r\) (其中 \(x_k\) 為 \(P_k\) 的橫坐標)——由 \(f'(x) = - \dfrac n {x^2}\) 可得該條件等價于 \(n x_r \leq (x_k + x_m)^2 y_r\) ——則容易得到如果再迭代下去的話,所有的 \(P_k + m\) 都不會落在 \(R'\) 內,也就能說已經二分完畢了,因此只需保留當前的棧重新回到步驟 2 進入下一輪迭代。
二:如果 \(\left| f' \left( x_k + x_m \right) \right| > r\),則接下來的向量還有可能落入 \(R'\) 中,因此令 \(l \gets m\) 后繼續二分。
這個做法的復雜度上界不超過 \(O \left( n^{1/3} \log n \right)\) ,因為斜率只有不超過\(O \left( n^{1/3} \right)\)個。(至于怎么證明文文也不會)。
后期函數的斜率非常小,都是 \(\dfrac 1k\) 的形式,會退化為 \(O(y)\),因此可以在 \(y \leq \sqrt[3]n\) 的部分中直接暴力計算
Code
#include <bits/stdc++.h> const int N=1000005; typedef long long ll; struct qwq {ll x, y;qwq (ll x0 = 0, ll y0 = 0) : x(x0), y(y0) {}inline qwq operator + (const qwq &B) const {return qwq(x + B.x, y + B.y);} }; ll n; int top; qwq stk[N]; inline void push(qwq x) { stk[++top]=x;} inline void putint(__int128 x) {static char buf[36];if (!x) {putchar(48); return;} int i = 0;for (; x; buf[++i] = x % 10 | 48, x /= 10);for (; i; --i) putchar(buf[i]); }inline bool check(ll x, ll y) {return n < x * y;}inline bool judge(ll x, qwq v) {return (__int128)n * v.x <= (__int128)x * x * v.y;}__int128 S1() {int i, crn = cbrt(n);ll srn = sqrt(n), x = n / srn, y = srn + 1;__int128 ret = 0;qwq L, R, M;push(qwq(1, 0)); push(qwq(1, 1));while(true) {for (L = stk[top--]; check(x + L.x, y - L.y); x += L.x, y -= L.y)ret += x * L.y + (L.y + 1) * (L.x - 1) / 2;if (y <= crn) break;for (R = stk[top]; !check(x + R.x, y - R.y); R = stk[--top]) L = R;for (; M = L + R, 1; )if (check(x + M.x, y - M.y)) push(R = M);else {if (judge(x + M.x, R)) break;L = M;}}for (i = 1; i < y; ++i) ret += n / i;return ret*2-srn*srn; } int main() {int T;for (scanf("%d", &T); T; --T) {scanf("%lld", &n); putint(S1());puts("");}return 0; }轉載于:https://www.cnblogs.com/Syameimaru/p/10197934.html
總結
以上是生活随笔為你收集整理的【SP26073】DIVCNT1 - Counting Divisors 题解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库多表关联查询
- 下一篇: Canny边缘检测算法原理及其VC实现详