LOJ簡單的函數
以這道題爲例子,講一下min_25篩如何用代碼實現。
首先min_25是一種亞線性篩,可以處理1e9以上的數據,老版min_25篩的複雜度爲,新版已經優化到,複雜度,我也不會證明。
據說常數比較大,確實是跑得快,但不過差距也不是特別大,我們還是說老版min_25篩吧。
min_25算法原理
上面這個博客寫得比較簡單,而且好理解,建議先大概看懂上面那個博客在看其他博客。
如果有錯誤,懇請各位指出來!
首先我先把min25的幾部分擺出來吧,min25就是難在質數求和。
首先是運用的要求
- 首先對於積性函數,當時,是一個關於的簡單多項式,簡單來說就是項數不能太多,冪次也不能太高,等會兒實現過程你就會發現。
- 能夠快速求得,這裏快速求得,起碼應該是log級別吧,等會兒代碼實現過程你就會發現了。
我們發現其實對於一個積性函數來說,其實我們見過第一個條件很多積性函數都滿足。
比如,發現當爲質數時,等於-1,而且第二個條件也很容易知道滿足,所以莫比烏斯函數,可以用min25篩求他的前綴和。
同理,對於歐拉函數,當爲質數時,等與,第二個條件,同樣也可以用min25篩。
再比如求,這個前綴和。總的來說很多積性函數的前綴和都可以秒殺。
我們看題
首先我們可以發現當是,其他質數時,即,可以認爲是多項式。同時第二個條件也滿足。
接下講一下第一部分,求g函數、
首先要處理關於質數的值,因爲積性函數質數點的值是關於質數的多項式,我們接下來的假設是有意義的。
- 首先我們設一個函數 換成中文就是,爲質數,或者的最小質因子大於,表示第個質數。
- 然後我們發現上面的每一個和都對應了一個狀態,我們考慮一些動態規劃的思想,找一找這個式子的遞推方程 。 我們考慮從,當時,表面在到的範圍內,不存在最小質因子爲的數,因此對於總和沒有貢獻,即時。
- 接下來考慮,這時從 ,明顯符合條件的數減少了,因此需要減去即,我們現在考慮一下,這個顯然應該是最小質因子是的一部分的值,我們減去最小質因子大於等於的那一部分值,我們不能從出發,這時,我們注意到,是一個完成積性函數,因此我們可以這樣狀態轉義,減去這一部分表示所有含有的值,因爲這裏多減去了質數的部分的,所以要加上這一部分的值,相當於一個容斥,如果不能很好的理解,可以手推模擬一下。
- 狀態轉移方程
我們顯然可以知道,這裏我們用表示質數集的大小,因此就表示範圍滿足條件的質數的和,因此這裏的質數我們,篩到就可以了,同時,注意沒有1哦。
其實這個一個過程就很像埃篩的思想,先篩出前個質數的情況,在推出的情況。
這裏我們上面要求是關於的簡單多項式,就是我們在求函數的時候方便求出來而已。我們來看看代碼具體如何實現。
- 首先我們可以看到上面的遞推,我們可以發現題目給出n的範圍一般很大的,直接開數組是開不下的,但不過因爲轉移的時候只和整除有關,所以我們在代碼實現時考慮數論分塊後的值,這樣的值大約有個。
- 我們首先要遞推函數,因此要確定的邊界,即,注意沒有包括1,其實1包不包括不重要,最後加上就是了,記住這裏的是假的,就是把當成全部是質數的情況來計算,纔有後面的一部分,因爲是多項式,因此少不了自然數冪求和,以及拉格朗日插值等等,所以就和上面的對應起來了,如果複雜了,肯定不好求,而且效率堪憂,建議自然數的冪次不要超過100,否則插值,會花費大量時間。
- 接下來就是正常的類似動態規劃的遞推了,注意應該在上一步中處理好整除分塊後的值。
int getid(ll x) {
if (x <= sqr)
return id1[x];
return id2[n / x];
}//sqr=sqrt(n);
void calc1() {
for (ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l), w[++m] = n / l;//假設全部是質數。
h[m] = (w[m] - 1) % mod;//常數項
g[m] = (w[m] % mod) * ((w[m] + 1) % mod) % mod;//一次項
g[m] *= inv2, g[m]--;
if (w[m] <= sqr)
id1[w[m]] = m;
else
id2[r] = m;
}
for (int j = 1; j <= cnt; j++) {
for (int i = 1; i <= m && pri[j] * pri[j] <= w[i]; i++) {
int k = getid(w[i] / pri[j]);
(g[i] -= 1ll * pri[j] * (g[k] - pre[j - 1]) % mod) %= mod;
(h[i] -= h[k] - j + 1) %= mod;
}
}
}
時間複雜的的證明…我不會。。。。看這裏吧時間複雜度
怎麼求和
說到這裏到底該怎麼求和呢。。。
- 我們還是設一個函數,中文就是的最小質因子大於等於注意這裏和上面不同是大於等於,不是大於了,並且沒有質數的必須計算的條件。
- 下面我們來思考,如何利用函數來輔助遞推函數。我可以把分成兩部分,一部分是質數的答案,一部分是合數的答案,關於質數部分的答案我們就可以了利用函數來遞推了即.這是質數部分的。
- 下面來考慮合數部分的值了。首先我們吧合數的最小質因子提出來,這些質因子都是大於等於的,可以得到爲什麼這樣就可以了?因爲是一個積性函數因此把最小質因子全部提出來然後相乘是肯定沒有問題,因此我們可以枚舉最小質因子以及冪次,就可以了,後面的那個一尾巴,是因爲在處理過程種所以的都被篩掉了,因此要補充進來。
- 因此,我們就可以利用遞歸進行狀態轉移了。同時從上面也可以看出來爲什麼要求條件二成立了。
代碼實現如下,我可以利用dfs實現狀態轉移,剩下的就是按照公式來就是了。那麼我們要求的就是最後加上1對應的函數值就可以了。
ll solve(ll x, int y) {
if (x <= 1 || pri[y] > x)
return 0;//邊界條件
int k = getid(x);//同樣注意分塊
ll ret = (g[k] - pre[y - 1] - h[k] + y - 1) % mod;
if (y == 1)
ret += 2;//注意2的特殊性
for (int i = y; i <= cnt && 1ll * pri[i] * pri[i] <= x; i++) {
ll t1 = pri[i], t2 = 1ll * pri[i] * pri[i];
for (int e = 1; t2 <= x; ++e, t1 = t2, t2 *= pri[i]) {
(ret += ((1ll * solve(x / t1, i + 1) * (pri[i] ^ e) % mod + (pri[i] ^ (e + 1)) % mod))) %= mod;
}
}
return ret;
}
好了LOJ6053已經講解完了。
總結一下
- 首先看函數滿不滿足條件。
- 其次找,然後分開項計算函數。
- 然後計算函數
這應該是標準模板吧。
然後min25還可以處理特殊質因子,最大質因子,次大質因子,最小質因子等問題。。。
感覺遲早要完。。。
下面來一道,洛谷上面的模板題
https://www.luogu.com.cn/problem/P5325
#include "bits/stdc++.h"
using namespace std;
inline int read() {
int x = 0;
bool f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
if (f) return x;
return 0 - x;
}
typedef long long ll;
const int maxn = 100010;
const ll mod = 1000000000 + 7;
ll quick(ll a, ll n, ll p) {
ll ans = 1;
for (; n; n >>= 1, a = a * a % p)
if (n & 1) ans = ans * a % p;
return ans;
}
int vis[maxn], sqr, cnt = 0;
ll suf1[maxn], suf2[maxn], inv2, inv6, n, pri[maxn];
void init(int lim) {
vis[1] = 1;
for (int i = 2; i <= lim; i++) {
if (!vis[i]) {
pri[++cnt] = i;
suf1[cnt] = suf1[cnt - 1] + i;
suf2[cnt] = (suf2[cnt - 1] + 1ll * i * i % mod) % mod;
}
for (int j = 1; j <= cnt && i * pri[j] <= lim; j++) {
vis[i * pri[j]] = 1;
if (i % pri[j] == 0) break;
}
}
}
int id1[maxn], id2[maxn], m = 0;
ll w[maxn << 1], g[maxn << 1], h[maxn << 1];
int getid(ll x) {
if (x <= sqr) return id1[x];
return id2[n / x];
}
ll sum1(ll x) {
x %= mod;
return (x + 1) * x % mod * inv2 % mod;
}
ll sum2(ll x) {
x %= mod;
return x * (x + 1) % mod * (2ll * x % mod + 1) % mod * inv6 % mod;
}
void calc1() {
for (ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
w[++m] = n / l;
g[m] = sum2(w[m]) - 1;
h[m] = sum1(w[m]) - 1;
if (w[m] <= sqr) id1[w[m]] = m;
else id2[r] = m;
}
for (int j = 1; j <= cnt; j++) {
for (int i = 1; i <= m && pri[j] * pri[j] <= w[i]; i++) {
int k = getid(w[i] / pri[j]);
(g[i] -= pri[j] * pri[j] % mod * (g[k] - suf2[j - 1]) % mod) %= mod;
(h[i] -= pri[j] * (h[k] - suf1[j - 1]) % mod) %= mod;
}
}
}
ll solve(ll x, int y) {
if (x <= 1 || pri[y] > x) return 0;
int k = getid(x);
ll ret = (g[k] - suf2[y - 1] - h[k] + suf1[y - 1]) % mod;
for (int i = y; i <= cnt && pri[i] * pri[i] <= x; i++) {
ll t1 = pri[i], t2 = pri[i] * pri[i];
for (int e = 1; t2 <= x; ++e, t1 = t2, t2 *= pri[i]) {
ll pk = quick(pri[i], e, mod);
(ret += pk * (pk - 1) % mod * solve(x / t1, i + 1) % mod) %= mod;
pk = pk * pri[i] % mod;
(ret += pk * (pk - 1) % mod) %= mod;
}
}
return ret;
}
int main() {
inv2 = quick(2, mod - 2, mod);
inv6 = quick(6, mod - 2, mod);
cin >> n;
sqr = sqrt(n);
init(sqr);
calc1();
ll ans = solve(n, 1) + 1;
cout << (ans + mod) % mod << endl;
return 0;
}
過年在家無事(太墮落了。。)無事於是想了想和杜教篩比一比速度。。。。
就拿洛谷上面那一道杜教篩模板題來對比一下
上面min25下面杜教篩。。
看來差距不是很大,杜教篩稍優
#include "bits/stdc++.h"
using namespace std;
inline int read() {
int x = 0;
bool f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = 0;
for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - '0';
if (f) return x;
return 0 - x;
}
typedef long long ll;
const int maxn = 100010;
const ll mod = 1000000000 + 7;
ll quick(ll a, ll n) {
ll ans = 1;
for (; n; n >>= 1, a = a * a)
if (n & 1) ans = ans * a;
return ans;
}
int vis[maxn], sqr, cnt = 0;
ll suf1[maxn], suf2[maxn], inv2, inv6, n, pri[maxn];
void init(int lim) {
vis[1] = 1;
for (int i = 2; i <= lim; i++) {
if (!vis[i]) {
pri[++cnt] = i;
suf1[cnt] = suf1[cnt - 1] + i;
}
for (int j = 1; j <= cnt && i * pri[j] <= lim; j++) {
vis[i * pri[j]] = 1;
if (i % pri[j] == 0) break;
}
}
}
int id1[maxn], id2[maxn], m = 0;
ll w[maxn << 1], g[maxn << 1], h[maxn << 1];
int getid(ll x) {
if (x <= sqr) return id1[x];
return id2[n / x];
}
ll sum2(ll x) {
return x * (x + 1) / 2;
}
void calc1() {
for (ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
w[++m] = n / l;
g[m] = sum2(w[m]) - 1;
h[m] = w[m] - 1;
if (w[m] <= sqr) id1[w[m]] = m;
else id2[r] = m;
}
for (int j = 1; j <= cnt; j++) {
for (int i = 1; i <= m && pri[j] * pri[j] <= w[i]; i++) {
int k = getid(w[i] / pri[j]);
g[i] -= 1ll * pri[j] * (g[k] - suf1[j - 1]);
h[i] -= h[k] - j + 1;
}
}
}
ll phi(ll p, ll k) {
return quick(p, k - 1) * (p - 1);
}
ll solve(ll x, int y) {
if (x <= 1 || pri[y] > x) return 0;
int k = getid(x);
ll ret = g[k] - suf1[y - 1] - h[k] + y - 1;
for (int i = y; i <= cnt && 1ll * pri[i] * pri[i] <= x; i++) {
ll t1 = pri[i], t2 = 1ll * pri[i] * pri[i];
for (int e = 1; t2 <= x; ++e, t1 = t2, t2 *= pri[i]) {
ret += 1ll * solve(x / t1, i + 1) * phi(pri[i], e) + phi(pri[i], e + 1);
}
}
return ret;
}
ll mu(ll k) {
if (k == 1) return -1;
return 0;
}
ll solve1(ll x, int y) {
if (x <= 1 || pri[y] > x) return 0;
int k = getid(x);
ll ret = -h[k] + y - 1;
for (int i = y; i <= cnt && 1ll * pri[i] * pri[i] <= x; i++) {
ll t1 = pri[i], t2 = 1ll * pri[i] * pri[i];
for (int e = 1; t2 <= x; ++e, t1 = t2, t2 *= pri[i]) {
ret += 1ll * solve1(x / t1, i + 1) * mu(e) + mu(e + 1);
}
}
return ret;
}
int main() {
int T = read();
init(100000);
while (T--) {
cin >> n;
sqr = sqrt(n);
m = 0;
calc1();
cout << solve(n, 1) + 1 << " ";
cout << solve1(n, 1) + 1 << "\n";
}
return 0;
}