LOJ #6202. 葉氏篩法(min_25 篩)


題意

\([L, R]\) 之間的素數之和 . \(L≤10^{10},2×10^{10} \le R \le 10^{11}\)

題解

一個有點裸的 min_25篩 ? 現在我只會篩素數的前綴和 , 合數的過幾天再學吧 .

首先推薦一波 yyb大佬博客 這個人很強 , 別那么fake就好啦

\(F(x) = x\) 顯然此處 \(F(x)\) 是完全積性函數 .

我們需要求的就是 $$\displaystyle \sum_{i=1}^{n} [i \in Prime] F(i)$$ .

這個就是 min_25篩的預處理部分 啦.

由 yyb博客 可得 .

對於下面這個表達式 ,

\[\displaystyle g(n,j)=\sum_{i=1}^ni[i\in P\ or \min(p)>P_j,p|i,p\in P\ ] \]

有如下一個遞推式 :

\[g(n,j)=\begin{cases} g(n,j-1)&P_j^2\gt n\\ g(n,j-1)-P_{j}[g(\frac{n}{P_j},j-1)-g(P_j-1,j-1)]&P_j^2\le n\end{cases} \]

這個直接實現是 \(\displaystyle O(\frac{n^{\frac{3}{4}}}{\log n})\) 的復雜度 qwq

然后考慮代碼實現 .

首先預處理前 \(\sqrt n\) 的素數 , (假設處理到了 \(lim\) )

以及 \(i=1 \sim lim\)\(F(x)\) 前綴和 . (此處可適當處理多一點 , 時間效率會提高)

然后假設我們當前考慮的是 \(g(n, m)\) .

直觀上共有三步 .

  1. \(P_{m+1}^2 > n\)\(n \le lim\) , 那么此時可以直接用之前預處理的答案 , 因為此時存在有貢獻的數只可能為素數 .
  2. \(m\) 一直縮小到 \(n < P_m^2\) . 這個利用了遞推式 \(P_j^2 > n\)\(g(n,j) = g(n, j - 1)\) 的那個定理 .
  3. 然后不斷將 \(m\) 下降, 直至下降到 \(0\) , 此間需要要遞歸計算 \(P_{j}[g(\frac{n}{P_j},j-1)+g(P_j-1,j-1)]\) 的值 , 其中后者 \(g(P_j-1,j-1)\) 可以用前面預處理 , 遞歸下去也只會有一層 . 而前者會不斷遞歸計算 . 其中 \(m\)\(0\) 的時候 , 就是邊界情況 : \(\displaystyle g(n, 0)=\sum_{i=2}^{n} i\) . 這個是很顯然的 , 因為此時所有數都計入了貢獻 , 但 \(1\) 是無法給予這個貢獻的 . (一開始此處調試許久... )

代碼

遞歸版本

直接按前面的式子模擬即可。

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define debug(x) cout << #x << ':' << x << endl
using namespace std;

void File() {
#ifdef zjp_shadow
	freopen ("6202.in", "r", stdin);
	freopen ("6202.out", "w", stdout);
#endif
}

const int N = 1e7 + 1e3, Lim = 1e7;

typedef __int128 ll;

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 ^ 48);
    return x * fh;
}

inline void Out(ll x, bool fir = true) { 
	if (!x) { if (fir) puts("0"); return ; }Out(x / 10, false); putchar (x % 10 + 48); if (fir) putchar ('\n'); 
}

int prime[N], cnt = 0; bitset<N> is_prime;

ll sump[N];

void Init(int maxn) {
	is_prime.set(); is_prime[0] = is_prime[1] = false;
	For (i, 2, maxn) {
		sump[i] = sump[i - 1];
		if (is_prime[i])
			prime[++ cnt] = i, sump[i] += i;
		For (j, 1, cnt) {
			register int res = i * prime[j];
			if (res > maxn) break;
			is_prime[res] = false;
			if (!(i % prime[j])) break;
		}
	}
}

inline ll Sum(ll x) { return x * (x + 1) / 2 - 1; }

int tot = 0;
ll Sump(ll n, int m) {
	if (n <= Lim && n < (ll)prime[m + 1] * prime[m + 1]) return sump[n];

	for (; n < (ll)prime[m] * prime[m]; -- m);

	ll res = Sum(n);
	for (; m; -- m)
		res -= (Sump(n / prime[m], m - 1) - /*Sump(prime[m] - 1, m - 1)*/ sump[prime[m] - 1]) * prime[m];

	return res;
}

inline ll Calc(ll x) { return Sump(x, cnt - 1); }

int main () {

	File();

	Init(Lim);

	ll l = read(), r = read(); Out(Calc(r) - Calc(l - 1));

	return 0;
}

非遞歸版本

如何做非遞歸呢?

考慮每次的 \(j\) 都是不斷減小的,就是我們會依次會除掉一個個質因子 \(p_j\)

我們從小到大枚舉每個質因子 \(p_j \le \lfloor \sqrt n \rfloor\) ,然后再從小到大枚舉每個 \(n\) 的塊 \(\displaystyle \lfloor \frac{n}{x} \rfloor\) (只有 \(\sqrt n\) 個)。

然后利用上面的式子直接遞推即可。

具體實現的時候由於只有 \(\sqrt n\) 個,我們對於 \(\le \sqrt n\) 的和 \(> \sqrt n\) 的進行分塊即可。

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

//typedef long long ll;
typedef __int128 ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

inline ll read() {
    ll x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

inline void Out(ll x) {
	if (!x) { puts("0"); }
	char stk[25]; int top = 0;
	for (; x; x /= 10) stk[++ top] = (x % 10) + 48;
	while (top) putchar (stk[top --]); putchar ('\n');
}

void File() {
#ifdef zjp_shadow
	freopen ("6202.in", "r", stdin);
	freopen ("6202.out", "w", stdout);
#endif
}

const int N = sqrt(1e11) * 2 + 10;

bitset<N> is_prime; 
int prime[N / 10], pcnt; ll sump[N / 10];

void Linear_Sieve(int maxn) {
	is_prime.set();
	is_prime[0] = is_prime[1] = false;

	For (i, 2, maxn) {
		if (is_prime[i]) {
			prime[++ pcnt] = i;
			sump[pcnt] = sump[pcnt - 1] + i;
		}
		for (int j = 1; j <= pcnt && 1ll * prime[j] * i <= maxn; ++ j) {
			is_prime[prime[j] * i] = false; if (!(i % prime[j])) break;
		}
	}
}

int d, id1[N], id2[N]; 

ll val[N], res[N];

#define id(x) (x <= d ? id1[x] : id2[n / (x)])

inline ll Sum(ll n) {
	return n * (n + 1) / 2 - 1;
}

ll Min25_Sieve(ll n) {
	int cnt = 0; d = sqrt((long long)(n));
	for (ll i = 1; i <= n; i = n / (n / i) + 1)
		val[id(n / i) = ++ cnt] = n / i, res[cnt] = Sum(n / i);

	For (i, 1, pcnt)
		for (int j = 1; j <= cnt && 1ll * prime[i] * prime[i] <= val[j]; ++ j)
			res[j] -= (res[id(val[j] / prime[i])] - sump[i - 1]) * prime[i];
	return res[id(n)];
}

int main () {

	File();

	ll l = read(), r = read();

	Linear_Sieve(sqrt(r + .5));

	Out(Min25_Sieve(r) - Min25_Sieve(l - 1));

	return 0;

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM