這個算法十分的強... 一般就是用於卡一道數論推結論題最后的\(20\)~\(30\)分...
被迫來學習QwQ
算法講解
這個一般用來篩一個積性函數在\(10^{10}\)左右的前綴和...
為了了解一下,可以看看 2016年國家候選隊論文中的任之洲的積性函數求和的幾種方法
講講一些套路吧...
首先有ymy大佬告訴我的一個神奇東西,這個似乎很有用...
若\(f*g=l\) (\(*\)為\(Dirichlet\)卷積,下同) 若\(G\)和\(L\)易求(\(g,l\)的前綴和) 則\(F\)易求 (\(f\)的前綴和).
即\(\displaystyle L(n)-g(1)F(n)=\sum _{i=2}^n g(i) F(\lfloor\frac{n}{i}\rfloor)\). 證明的話可以參照后面的推導來證。
\(Dirichlet\)卷積 :
定義兩個數論函數\(f(x),g(x)\)的\(Dirichlet\)卷積 (記作\((f*g)(x)\))
\[\displaystyle (f*g)(n)=\sum _{d|n} f(d)g(\frac{n}{d}) \]
引入例題
51nod 1244 莫比烏斯函數之和
題意
求\(\displaystyle \sum _{i=a}^{b} \mu(i)\). (\(2\le a \le b \le 10 ^ {10}\))
題解
這道題一個裸的杜教篩...求個前綴和,再減一下就行了...
有一個有用的式子\(\displaystyle \sum _{d|n} \mu(d)=[n=1]\).(這個可以見我之前那篇博客)
解法一:
直接套用之前那個式子....
\(\mu * 1=\epsilon\) 此處\(\epsilon(x) =[x=1]\) 也就是上面那個
右邊前綴和都很好求\(\displaystyle \sum _{i=1}^{n} \epsilon(i)=1\).
然后你代入到之前那個式子就有 (令\(\displaystyle M(i)=\sum_{i=1}^{n} \mu(i)\))
解法二
我們來手推一下上面的式子qwq
然后就有個很顯然的式子(只有\(i=1\)時候有貢獻)
然后更換枚舉項,就是考慮每個\(d\)的對於它倍數的貢獻,然后交換和式就行了。
接下來我們就可以提出\(i=1\)時的一項,也就是\(\displaystyle \sum _{j=1}^{\lfloor \frac{n}{1}\rfloor=n} \mu(j)\)
我們如果令\(\displaystyle M(n)=\sum_{i=1}^{n} \mu(i)\)就有和之前那個一樣的式子了
然后我們對於這個式子就可以遞歸求解了,時間復雜度是\(\Theta(n^{\frac{2}{3}})\).
但是一定要記得要開個哈希表存儲一下,不然時間復雜度是個錯的!!!(來自ymy大佬的原話)
這個哈希表可以只開\(\sqrt n\)大小,因為\(\lfloor \frac{n}{i} \rfloor\)只有這么多種取值.
代碼我看了一下fjzzq大佬的博客 emmm... (同屆大佬太恐怖了)
我自己寫的一個... 注意一定要全程long long
不然有詭異的錯誤....
代碼
#include <bits/stdc++.h>
#define For(i, l, r) for(register ll i = (l), _end_ = (ll)(r); i <= _end_; ++i)
#define Fordown(i, r, l) for(register ll i = (r), _end_ = (ll)(l); i >= _end_; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;
typedef long long ll;
bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}
inline ll read() {
ll x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar() ) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar() ) x = (x<<1) + (x<<3) + (ch ^ '0');
return x * fh;
}
void File() {
#ifdef zjp_shadow
freopen ("1244.in", "r", stdin);
freopen ("1244.out", "w", stdout);
#endif
}
const int N = 2e6 + 1e3;
ll mu[N], Limit, prime[N], cnt;
ll summu[N];
bitset<N> is_prime;
void Mu_Init(int maxn) {
mu[1] = 1; int res; is_prime.set();
is_prime[0] = is_prime[1] = false;
For (i, 2, maxn) {
if (is_prime[i]) { prime[++cnt] = i; mu[i] = -1; }
For (j, 1, maxn) {
res = prime[j] * i;
if (res >= maxn) break ;
is_prime[res] = false;
if (i % prime[j]) mu[res] = -mu[i];
else { mu[res] = 0; break ; }
}
}
For (i, 1, maxn) summu[i] = summu[i - 1] + mu[i];
}
ll Size;
ll p1[N], p2[N], n;
inline ll &App(ll x) { if (x < Size) return p1[x]; return p2[n / x]; }
inline ll Mu_Sum_(ll x) {
if (x <= Limit) return summu[x];
ll &htr = App(x); if (htr != p2[0]) return htr;
ll res = 1, Nextx;
For (i, 2, x) {
Nextx = x / (x / i);
res -= 1ll * (Nextx - i + 1) * Mu_Sum_(x / i);
i = Nextx;
}
return htr = res;
}
inline ll Mu_Sum(ll x) {
Size = (int)(sqrt(x) + 0.5);
Set(p1, -2333); Set(p2, -2333); n = x;
return Mu_Sum_(x);
}
int main () {
File();
ll a, b;
cin >> a >> b;
Limit = N - (int)1e3; Mu_Init(Limit);
cout << Mu_Sum(b) - Mu_Sum(a - 1) << endl;
//cerr << "Time used: " << (double)clock() / CLOCKS_PER_SEC << "s" << endl;
return 0;
}
BZOJ3944 : Sum
然后在簡單講一下求\(\varphi\)(歐拉函數)的前綴和吧...
題意 :
求\(\displaystyle \sum_{i=1}^{n} \varphi(i)\) 和 \(\displaystyle \sum _{i=1}^{n} \mu(i)\). (\(n \le 2^{31}-1\))
題解 :
其實和上一道題差不多啦...多求了一個\(\varphi\)嘛...
只要知道\(\varphi\)也有一個類似的結論$$\displaystyle \sum_{d|n} \varphi(d)=n$$
這個我不會證明 只有一個感性理解的方法...(來自具體數學)
你考慮所有分母為\(n\)的真分數和\(\frac{n}{n}\)(共有\(n\)個)
將分子和分母約去后,其他每個\(d|n\)的\(d\)都對它具有\(\varphi(d)\)的貢獻咯...
例如\(n=6\).
約分后就是
這個就比較顯然了, 應該可以寫出證明的...(太懶也太菜了..)
然后像上一題一樣的啦~ 上道題兩個解法都能用用...
\(\varphi * 1=id\) \((id(x)=x)\) 我們令\(\displaystyle \phi(n) = \sum _ {i=1}^{n} \varphi(i)\).
就有一個顯然的式子
這個同上就行咯...
hihoCoder #1456 : Rikka with Lattice
這題也是一道好題!~
如果想看的,就在我另一篇博客上 無恥騙點擊...