hdu 2879 HeHe 数论 积性函数 优化技巧

题目

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2879

题目来源:学弟问的。

简要题意: f(n) x2xmodn(x[0,n)) 的解的个数,
     i=1Nf(i)modM

数据范围: 1N107;0<M109

题解

解这题很好的一个方法是暴力打表然后观察,然后会发现 f(n) n 的素因数个数有关。

x2xmodn x(x1)0modn nx(x1) gcd(n,x(x1))=n

显然 f(1)=1

对于素数 p 来说f(p)=2因为根据素数定义 x[1,p),gcd(x,p)=1

gcd(p,x(x1))={gcd(p,x1)gcd(p,x)=1            x>1p                                                          x=0,1
对于 pa 即素数的幂次 f(pa)=2 ,因为 gcd(pa,x) pb(b<a) gcd(x,x1)=1

再去考虑合数的情况,此处要使用积性函数的性质。

考虑互素的两个数 p,q ,则 f(pq)=f(p)f(q)

这里我无法给出证明,好难啊,想了两天了还是没成功,网上的各种证明没有让我信服的。

这个 属于稍微有点道理的,但是其实两个质数的情况不能直接推出两个互质的数的情况。

假设积性于是 f(n)=i=1kf(paii)=2k

n 的素因数个数为λ(n) f(n)=2λ(n)

实现

实际写代码的话用结论直接去做肯定是会超时的。

需要转化为求 n 内所有素数的倍数的个数之和,设p1,p2pk N 内的所有质数。

g(N)=i=1kN/pi
i=1Nf(i)=2g(N)

这个玩意可以用很多方法去求,我为了改进效率试了很多种。

预处理素数+分块求和+bitset压缩内存

不分块,不用bitset也能过,但是这么搞之后可以提速,减少内存消耗。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <bitset>

#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
inline LL powmod(LL a,LL b, LL MOD) {LL res=1;a%=MOD;for(;b;b>>=1){if(b&1)res=res*a%MOD;a=a*a%MOD;}return res;}
// head
LL MOD;
const int N = 10000000+500;
const int M = 670000;

bitset<N> vis;
int p[M];
int tot = 0;

void getPrime(int n) {
    for (int i = 2; i < n; i++) {
        if (!vis[i]) p[++tot] = i;
        for (int j = 1; j<=tot && i*p[j]<n; j++) {
            vis[i*p[j]] = true;
            if (i%p[j]==0) break;
        }
    }
}

int solve(int n) {
    int en, val, pw = 0, be = 1;
    while (p[be] <= n) {
        val = n/p[be];
        en = upper_bound(p+1, p+tot, n/val)-p;
        pw += val*(en-be);
        be = en;
    }
    return powmod(2, pw, MOD);
}

int main() {
    getPrime(N);
    int t, n;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%I64d", &n, &MOD);
        printf("%d\n", solve(n));
    }
    return 0;
}

埃氏筛法预处理答案

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <stack>
#include <queue>
#include <string>
#include <vector>
#include <set>
#include <map>

#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define sz(x) ((int)(x).size())
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
LL powmod(LL a,LL b, LL MOD) {LL res=1;a%=MOD;for(;b;b>>=1){if(b&1)res=res*a%MOD;a=a*a%MOD;}return res;}
// head
LL MOD;
const int N = 10000000+5;
int res[N];
int tot = 0;
void getPrime(int n) {
    for (int i = 2; i <= n; i++) {
        if (!res[i]) {
            for (int j = i; j<=n; j += i) {
                res[j]++;
            }
        }
        res[i] += res[i-1];
    }
}

int main() {
    getPrime(1e7);
    int t, n;
    scanf("%d", &t);
    while (t--) {
        scanf("%d%I64d", &n, &MOD);
        printf("%I64d\n", powmod(2, res[n], MOD));
    }
    return 0;
}

欧拉筛预处理答案

void getPrime(int n) {
    int temp;
    for (int i = 2; i <= n; i++) {
        if (!res[i]) p[++tot] = i, res[i]++;
        for (int j = 1; j<=tot && (temp=i*p[j])<=n; j++) {
            (res[temp] = res[i])++;
            if (i%p[j]==0) {
                res[temp]--;
                break;
            }
        }
        res[i] += res[i-1];
    }
}

死扣加快好不容易到156ms再也上不去

inline void getPrime(int n) {
    int temp, l = n/2;
    for (int i = 2; i <= l; i++) {
        if (!res[i]) p[++tot] = i, res[i]++;
        for (int j = 1; j<=tot && (temp=i*p[j])<=n; j++) {
            (res[temp] = res[i])++;
            if (i%p[j]==0) {
                res[temp]--;
                break;
            }
        }
        res[i] += res[i-1];
    }
    for (int i = l+1; i <= n; i++) {
        if (!res[i]) res[i]++;
        res[i] += res[i-1];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值