NOIP模拟赛 数论 积性函数 + BSGS + Lucas模进制数位Dp

本文解析了三种数论问题的高效算法实现,包括线性筛法解决积性函数问题、BSGS算法快速找到等比数列的周期以及五进制下的数位DP解决组合数计数问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem 1. facsum

这里写图片描述

题解

我们发现后面那个sigma里都是积性函数, 积性函数相乘也是积性函数. 我们可以线性筛筛除后面那个就可以.
考虑后面那个和式设为积性函数g, p为质数的话, g(p) = 2 - p. g(p ^ 2) = 3 - 2p. g(p ^ 3) = 4 - 3p, 以此类推. 然后用线性筛来筛, 若i 与 pr[j]互质的话, g(i * p[j]) = g[i] * g[pr[j]], 这是积性函数的性质.
如果不互质的, 由于线性筛某个数一定是被最小的质因子筛掉的, 所以记录一下某个数最小质因子的指数且值为多少. 在i与pr[j]不互质的情况下, 将i的pr[j]质因子抽出来使之互质即可.

#include<stdio.h>
typedef long long dnt;
const int mod  = 1e9 + 7;
const int maxn = 1e7 + 5;
int n, m, tot;
bool mark[maxn + 5];
int pr[maxn + 5], t[maxn + 6];
dnt g[maxn + 5], f[maxn + 5], h[maxn + 5];
inline dnt mpow(dnt a){
    dnt ans = 1, b = m;
    while(b){
        if(b & 1) ans = ans * a % mod;
        a = a * a % mod, b >>= 1;
    }
    return ans;
}
inline void Linear_sieve(){
    g[1] = 1; h[1] = 1;
    for(int i = 2; i < maxn; ++i){
        if(!mark[i]) pr[++tot] = i, g[i] = 2 - i, t[i] = 1, h[i] = i;
        for(int j = 1; j <= tot && i * pr[j] < maxn; ++j){
            int k = pr[j] * i;
            mark[k] = true;
            if(!(i % pr[j])){
                t[k] = t[i] + 1;
                h[k] = h[i] * pr[j] % mod;
                g[k] = (g[i / h[i]] * (t[k] + 1 - t[k] * pr[j])) % mod;
                break;
            }
            h[k] = pr[j];
            t[k] = 1;
            g[k] = g[i] * g[pr[j]] % mod;
        }
    }
}
inline void init_f(){
    for(int i = 1; i <= n; ++i)
        f[i]= ((f[i - 1] + mpow(i) * g[i]) % mod + mod) % mod;
}
int main(){
    freopen("facsum.in", "r", stdin);
    freopen("facsum.out", "w", stdout);
    scanf("%d%d", &n, &m);
    Linear_sieve();
    init_f();
    printf("%I64d\n", f[n]);
    return 0;
}

Problem 2. group

Input file: group.in
Output file: group.out
Time limit: 1 second
Memory limit: 256 MB
Mr.Hu 最近在研究等比数列,即形如:
a; a^1; a^2; a^3; a^n;
现在,Mr.Hu 想知道,对于给定的非负整数a,上面这个无穷数列在摸mod 意义下有多少项是本质不同
的。(保证gcd(a; mod) = 1)。
Input
第1 行一个整数:T,表示数据组数。
接下来T 行,每行两个整数:a mod。
Output
对于每组数据,输出一行,包含一个整数,表示模意义下本质不同的数有多少个。
Sample
group.in group.out
21
3
2 5
14
对于第一组数据,数列是:1; 1; 1; 1;
对于第二组数据,数列(取模以后)是:2; 4; 3; 1; 2; 4; 3; 1; ,总共有4 个本质不同的数。
Note
• 对于30% 的数据,0 a 103,1 mod 103;
• 对于100% 的数据,0 a 2 109,1 mod 2 109,且保证gcd(a; mod) = 1,1 T 100。

题解

求出最小循环节. 求出最小x使a ^ x 同余 1即可. BSGS即可.

#include<stdio.h>
#include<cmath>
#include<cstring>
#define clear(a) memset(a, 0, sizeof(a))
const int S = 100007;
typedef long long dnt;
int p, T, num;
int h[S + 5];
struct edge{
    dnt v;
    int nxt, c;
}e[S * 2];
inline void insert(dnt x, int y){
//  printf("%I64d %d\n", x, y);
    dnt u = x % S;
    for(int i = h[u]; i; i = e[i].nxt)
        if(e[i].v == x){
            e[i].c = y; return;
        }
    e[++num].v = x;
    e[num].c = y;
    e[num].nxt = h[u];
    h[u] = num;
}
inline int query(dnt x){
//  printf("%I64dxxxxxxxx\n", x);
    dnt u = x % S;
    for(int i = h[u]; i; i = e[i].nxt)
        if(e[i].v == x) return e[i].c;
    return -1;
}
inline dnt BSGS(dnt a, dnt b){
    dnt m = ceil(sqrt(p)), mul = 1, val = b % p;
    clear(h), num = 0;
    insert(val, 0);
//  printf("%I64dm\n", m);
    for(int j = 1; j <= m; ++j){
        mul = mul * a % p;
        val = mul * b % p;
        if(j != m) insert(val, j);
    }
    dnt am = mul;
//  printf("%I64dam\n", am);
    mul = 1;
    for(int i = 1; i <= m; ++i){
        mul = mul * am % p;
        int j = query(mul);
        if(~j) return i * m - j;
    }
    return -1;
}
int main(){
    freopen("group.in", "r", stdin);
    freopen("group.out", "w", stdout);
    dnt a;
    scanf("%d", &T);
    while(T--){
        scanf("%I64d%d", &a, &p);
        printf("%I64d\n", BSGS(a, 1));
    }
    return 0;
}

Problem 3. ccount

这里写图片描述

题解

发现lucas实际就是将n和m模数进制下拆分. 那么就在5进制下数位dp, 看某一位C(n, i)是不是0即可. 注意n和i都是5进制下的.
用r的答案减去l-1的答案.

#include<stdio.h>
#include<cstring>
#define deeper(a) memset(a, -1, sizeof(a))
const int P = 5;
const int maxn = 1e2;
typedef long long dnt;
int T, utot, dtot;
int c[P][P], up[maxn], down[maxn];
dnt dp[maxn][6];
inline void init(){
    for(int i = 0; i < P; ++i)
        for(int j = 0; j <= i; ++j){
            if(j == 0 || j == i) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
        }
}
inline void calc(dnt x, int *arr, int &tot){
    tot = 0;
    if(!x)
        arr[++tot] = 0;
    while(x){
        arr[++tot] = x % P;
        x /= P;
    }
}
dnt dfs(int pos, int r, int lim){
    if(~dp[pos][r] && !lim) return dp[pos][r];
    if(!pos) return !r;
    dnt ed = 0;
    int ban = (lim) ? down[pos] : P - 1;
    for(int i = 0; i <= ban; ++i)
        ed += dfs(pos - 1, r * c[up[pos]][i] % P, lim && i == ban);
    if(!lim) dp[pos][r] = ed;
    return ed;
}
inline dnt solve(dnt x){
    calc(x, down, dtot);
    return dfs(dtot, 1, true);
}
int main(){
    freopen("ccount.in", "r", stdin);
    freopen("ccount.out", "w", stdout);
    init();
    scanf("%d", &T);
    while(T--){
        dnt l, r, n;
        scanf("%I64d%I64d%I64d", &l, &r, &n);   
        deeper(dp);
        calc(n, up, utot);
        dnt ans = solve(r);
        if(l) ans -= solve(l - 1);
        printf("%I64d\n", ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值