好像是一种比较新的筛法,网上资料都是18年的
赶上时代潮流了??
据说踩爆了一种叫洲阁筛的筛法?
用途
筛一些比较神奇的函数F前缀和。(或者询问区间内什么的)
F需要是积性函数(并不是所有都可以,但完全积性函数一定可以。(题目所求可以转换为积性函数啥的)
复杂度
空间n^0.5,时间据说为
2s大概可以做两次1e10吧。
Part1(筛法递推)
下文中,记
表示a的最小质因子。
一个数x要么是质数,要么
。
首先,我们来求出所有质数的F和。
设
表示
Pj表示第j个质数。(即筛法进行到Pj时的状态)
其中j的取值只有
个,按j分层,我们要求的是筛法进行完毕的状态
初始状态
我们将所有数都视作质数来预处理,然后一个个质数去筛,将合数筛去,这样剩下的就是质数的F和。
容易推出g的递推式,用
来推
,即减去所有最小质因子为
的数的F和。
后面括号内sumf(j)是指
晒微解释一下式子的含义:
考虑任意一个需要被筛出的数
,即
。
注意需要同时满足
这样的数的F和其实可以用g表示出来。但同时注意到g内有一段小于
的质数,需要减去。
*注意此处
可能小于Pj,此时式子不正确。但当
时显然
根据常识,第一维大小只有
,离散下来存好。具体可能需要看标。
下面在筛质数和。
int getid(ll x) {
if (x <= P) return id[x]; else return id2[n/x];
}
//p是质数数组,pre[x]表示p[2]+p[3]...+p[x]
P = sqrt(n);
for (ll l = 1; l <= n;) {
ll v = n / l, r = n / v;
if (v <= P) id[v] = ++m; else id2[n/v] = ++m;
w[m] = v;
g[m] = (2+v)*(v-1)/2;
l = r + 1;
}
//注意w是递减的。
for (int j = 1; p[j] <= P; j++) {
for (int i = 1; i <= m; i++) {
if (p[j] * p[j] > w[i]) break;
g[i] -= p[j] * (g[getid(w[i] / p[j])] - pre[j-1]);
}
}
Part2(答案统计)
求好了所有质数的和,就可以考虑如何求真·前缀和了。
有直接计算 / 递推两个方法。优缺点在最后。
直接计算版
类似g的定义,
设
表示
(注意是R(x)大于等于Pj而不是大于Pj)
分两个部分来计算:
1.质数
2.合数,通过枚举其最小质因子与次幂来统计。
对于f(i,j),显然质数部分就是
合数部分,先枚举最小质因子与次幂 ,当然要满足 。
我们要将符合这一部分的F算进来,这样的数
由于F是积性函数,其实就是
另外,因为f函数是不包括1的,所以质数的次幂会漏算。加上就行。
#define F(x,y) (...)
ll ask(ll x,ll i) {
if (x <= 1 || prime[i] > x) return 0;
ll ans = g[getid(x)] - pre[i-1];
for (ll j = i; j <= prime[0] && prime[j] * prime[j] <= x; j ++) {
ll r = prime[j];
for (ll e = 1; r * prime[j] <= x; e++, r *= prime[j]) {
ans = (ans + F(prime[j], e) * ask( x / r, j + 1 ) + F(prime[j], e+1)) % mo;
}
}
return ans;
}
递推版
使用了求g的思想。
*为了方便递推,f的定义改变了。
设
表示
初值 即质数和g.
相比于
,需要加上
同样,这样的F实际上就是 再整体乘上 ,此处也漏了质数幂,需要补上。
同样在筛数的和(虽然没有什么意义…)。
for (int j = p[0]; j; j--) {
if (p[j] * p[j] > n) continue;
for (int i = 1; i <= m; i++) {
if (p[j] * p[j] > w[i]) break;
ll l = p[j];
for (int e = 1; l * p[j] <= w[i]; e++,l*=p[j]) {
g[i] += l * (g[getid(w[i]/l)] - pre[j]) + l * p[j];
}
}
}
方法比较
若只需要求一个f,那么直接计算会有比较小的常数(相比后者1/2到1/3)。
但后者可以一次处理出对于所有f(n),f(n/2),f(n/3)….f(1)的值。
例题
LOJ简单的函数
直接计算法,套式子就行。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 1000, mo = 1e9 + 7, inv2 = 500000004;
typedef long long ll;
ll n,P;
ll id1[N],id2[N],w[2*N],m,g[2*N],h[2*N],f[2*N];
ll prime[N],pre[N];
bool is[N];
void init() {
for (ll i = 2; i <= 1e5; i ++) {
if (!is[i]) prime[++prime[0]] = i, pre[prime[0]] = (pre[prime[0] - 1] + i) % mo;
for (ll j = 1; j <= prime[0] && prime[j] * i <= 100000; j ++) {
is[prime[j] * i] = 1;
if (i % prime[j] == 0) break;
}
}
}
int getid(ll x) {
if (x <= P) return id1[x]; else return id2[n / x];
}
void calc_gh() {
for (ll l = 1; l <= n; ) {
ll v = n / l, r = n / v;
if (v <= P) id1[v] = ++m; else id2[r] = ++m;
w[m] = v;
ll z = v % mo;
//sum 2 ~ v
g[m] = (2 + z) * (z - 2 + 1) % mo * inv2 % mo;
h[m] = z - 1;
l = r + 1;
}
for (ll i = 1; i <= prime[0]; i++) {
ll z = prime[i];
for (ll j = 1; j <= m && z * z <= w[j]; j++) {
int op = getid(w[j]/z);
g[j] = (g[j] - prime[i] * (g[op] - pre[i-1]) % mo) % mo;
h[j] = (h[j] - (h[op] - (i - 1))) % mo;
}
}
for (int i = 1; i <= m; i++) {
g[i] = (g[i] + mo) % mo;
f[i] = (f[i] + mo) % mo;
// printf("%lld %lld\n",g[i],h[i]);
f[i] = (g[i] - h[i]) % mo;
}
}
#define F(x,y) ((x)^(y))
ll ask(ll x,ll i) {
if (x <= 1 || prime[i] > x) return 0;
ll ans = f[getid(x)] - (pre[i-1] - (i - 1)); if (i == 1) ans+=2;
for (ll j = i; j <= prime[0] && prime[j] * prime[j] <= x; j ++) {
ll r = prime[j];
for (ll e = 1; r * prime[j] <= x; e++, r *= prime[j]) {
ans = (ans + F(prime[j], e) * ask( x / r, j + 1 ) + F(prime[j], e+1)) % mo;
}
}
// printf("%lld %lld %lld\n",x,i,ans);
return ans;
}
int main() {
cin>>n; P = sqrt(n);
init();
calc_gh();
printf("%lld\n",((ask(n,1) + 1)%mo+mo)%mo);
}
jzoj5594 最大真因数
使用了递推法,方便在途中统计答案。(我是枚举最小质因子来统计的。)
下面有两句break是带来巨大常数优化的,务必要加上。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 2 * 750000;
ll L,R;
ll g[N+10],f[N+10],id[N+10],id2[N+10],m,w[N+10],pre[N+10];
ll p[N+10],P;
bool bz[N+10];
void init() {
for (int i = 2; i <= N; i++) {
if (!bz[i]) p[++p[0]] = i,pre[p[0]] = pre[p[0]-1] + i;
for (int j = 1; j <= p[0] && i * p[j] <= N; j++) {
bz[i * p[j]] = 1;
}
}
}
ll zz;
int getid(ll x) {
if (x <= P) return id[x]; else return id2[zz/x];
}
ll calc(ll n) {
if (n <= 1) return 1;
zz = n;
P = sqrt(n);
m = 0;
memset(id,0,sizeof id); memset(id2,0,sizeof id2);
memset(g,0,sizeof g); memset(f,0,sizeof f);
for (ll l = 1; l <= n;) {
ll v = n / l, r = n / v;
if (v <= P) id[v] = ++m; else id2[n/v] = ++m;
w[m] = v;
g[m] = (2+v)*(v-1)/2;
l = r + 1;
}
for (int j = 1; p[j] <= P; j++) {
for (int i = 1; i <= m; i++) {
if (p[j] * p[j] > w[i]) break; //不加这句会TLE!!!
g[i] -= p[j] * (g[getid(w[i] / p[j])] - pre[j-1]);
}
}
ll ret = 0, sup = 0;
for (int j = p[0]; j; j--) {
if (p[j] * p[j] > n) continue;
for (ll u = p[j]; u*p[j] <= n; u*=p[j]) {
ret += u/p[j] * (g[getid(n/u)] - pre[j]) + u;
}
for (int i = 1; i <= m; i++) {
if (p[j] * p[j] > w[i]) break; //不加这句会TLE!!!
ll l = p[j];
for (int e = 1; l * p[j] <= w[i]; e++,l*=p[j]) {
g[i] += l * (g[getid(w[i]/l)] - pre[j]) + l * p[j];
}
}
}
return ret + 1;
}
int main() {
freopen("factor.in","r",stdin);
// freopen("factor.out","w",stdout);
cin>>L>>R;
init();
cout<<calc(R) - calc(L-1)<<endl;
}
复杂度口胡
看着看着代码好像发现了前一部分的复杂度怎么证
大概是
事实上并不是每一个i都是质数,所以这是个非常宽的上界。
这个复杂度是
,后面那一部分是
。
证法是找一个可以求导之后等于
的函数,转换为两个端点函数值相减。
不难发现这个函数就是
。因此等于
,也就是
*为什么我觉得这个证法的界也超松的….
至于那个log怎么来的,大概是素数的缘故吧…不太关心