组合数学与数论函数专题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_37517391/article/details/82718970

组合数学专题

专题简介

本专题包含了一些组合数学中常见的套路和方法,如拉格朗日插值,动态规划,容斥原理,狄利克雷卷积,线性筛,杜教筛 等等.

目录

  1. 2018 四川省赛GRISAIA (数论分块)
  2. HDU 6428 Calculate (狄利克雷卷积,线性筛)
  3. BZOJ4559 成绩比较 (动态规划,拉格朗日插值)
  4. BZOJ 2633 已经没有什么好害怕的了 (容斥森林,动态规划)
  5. BZOJ 3930 选数 (容斥原理,动态规划)
  6. 徐州网络赛 D Easy Math (积性函数,线性筛)
  7. NWERC2015 Debugging (动态规划,数论分块)

四川省赛 GRISAIA

问题描述

i = 1 n j = 1 i n % ( i j ) \sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)} ,其中 1 n 1 0 11 1 \le n \le 10^{11}

引入的一些性质

n x y = n x y \lceil \frac{n}{xy} \rceil = \lceil \frac{\lceil \frac{n}{x} \rceil}{y} \rceil
n x y = n x y \lfloor \frac{n}{xy} \rfloor = \lfloor \frac{\lfloor \frac{n}{x} \rfloor}{y} \rfloor

解法

先对原问题进行变形:

i = 1 n j = 1 i n % ( i j ) \sum_{i=1}^{n}\sum_{j=1}^{i}{n \% (i * j)}

= i = 1 n j = 1 i ( n ( n i j ) i j ) = \sum_{i=1}^{n}\sum_{j=1}^{i}{(n - \lfloor (\frac{n}{ij}) \rfloor * ij)}

只需要求:

i = 1 n j = 1 i ( n i j ) i j \sum_{i=1}^{n}\sum_{j=1}^{i}{\lfloor (\frac{n}{ij}) \rfloor * ij}

= 1 2 i = 1 n j = 1 n ( ( n i j ) i j ) + 1 2 i = 1 n n i 2 i 2 = \frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}{(\lfloor (\frac{n}{ij}) \rfloor * ij}) + \frac{1}{2}\sum_{i=1}^{n}{\lfloor \frac{n}{i^2} \rfloor * i^2}

只需要求:

i = 1 n j = 1 n ( ( n i j ) i j ) \sum_{i=1}^{n}\sum_{j=1}^{n}{(\lfloor (\frac{n}{ij}) \rfloor * ij})

这里我们就要使用引入的性质了:

= i = 1 n j = 1 n ( i n i j j ) = \sum_{i = 1}^{n}\sum_{j = 1}^{n}(i * \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j)

= i = 1 n i j = 1 n ( n i j j ) = \sum_{i = 1}^{n}{i}\sum_{j = 1}^{n}( \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j)

记录

f ( n ) = i = 1 n n i i f(n) = \sum_{i = 1}^{n}{\lfloor \frac{n}{i} \rfloor} * i

显然

i = 1 n j = 1 n ( i n i j j ) = i = 1 n i f ( n i ) \sum_{i = 1}^{n}\sum_{j = 1}^{n}(i * \lfloor \frac{\lfloor \frac{n}{i} \rfloor}{j} \rfloor * j) = \sum_{i = 1}^{n}{i*f(\lfloor \frac{n}{i} \rfloor)}

显然 f ( n ) f(n) 的计算可以数论分块,然后上面部分的计算也可以分块.


HDU6428 [Calculate]

知识清单

  1. 欧拉函数
  2. 莫比乌斯函数
  3. 莫比乌斯反演
  4. 狄利克雷卷积
  5. 线性筛

题解

要求计算 i = 1 A j = 1 b k = 1 C ( ϕ ( g c d ( i , j 2 , k 3 ) ) ) \sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}(\phi(gcd(i,j^2,k^3)))
我们先对式子进行变换,比较套路一步是改为枚举 g c d gcd ,然后统计 g c d gcd 出现的次数,即:

= d = 1 ϕ ( d ) ( i = 1 A j = 1 b k = 1 C [ g c d ( i , j 2 , k 3 ) = d ] ) = \sum_{d = 1}{\phi{(d)}}{(\sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d])}

对上述式子中的 i = 1 A j = 1 b k = 1 C [ g c d ( i , j 2 , k 3 ) = d ] \sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d] 再进行莫比乌斯反演:

g ( d ) = i = 1 A j = 1 b k = 1 C [ g c d ( i , j 2 , k 3 ) = d ] g(d) = \sum_{i = 1}^{A}\sum_{j = 1}^{b}\sum_{k = 1}^{C}[gcd(i,j^2,k^3)=d]

并设 G [ n ] = n d g ( d ) G[n] = \sum_{n|d}{g(d)}

那么显然有 g ( n ) = n d μ ( d n ) G ( d ) g(n) = \sum_{n|d}\mu(\frac{d}{n})G(d)

那么要求的式子可以写成

= d = 1 ϕ ( d ) g ( d ) = d = 1 ϕ ( d ) d t μ ( t d ) G ( t ) = \sum_{d = 1}{\phi{(d)}} g(d) = \sum_{d = 1}{\phi{(d)}}\sum_{d|t}\mu(\frac{t}{d})G(t)

由于 ϕ ( x ) \phi(x) μ ( x ) \mu(x) 都是积性函数,故将 G [ t ] G[t] ϕ ( d ) \phi(d) 的位置做交换,使之形成狄利克雷卷积.

= t = 1 G ( t ) d t μ ( t d ) ϕ ( d ) = \sum_{t=1}G(t)\sum_{d|t}\mu(\frac{t}{d})\phi(d)

在这里,记 F ( x ) = d t μ ( t d ) ϕ ( d ) F(x) = \sum_{d|t}\mu(\frac{t}{d})\phi(d)

F ( x ) F(x) 是积性函数的狄利克雷卷积,因此 F ( x ) F(x) 也是积性函数,可以用线性筛法求得.

线性筛所能用到的信息:

F ( 1 ) = 1 F(1) = 1

F ( p x ) = F ( p x 1 ) p F(p^x) = F(p^{x-1})*p

F ( p ) = p 2 2 p + 1 F(p) = p^2-2*p+1

有关 G ( n ) G(n) 的计算:

n = p i a i n = \prod{p_i^{a_i}}

则定义

g 2 ( n ) = p i a i 2 g_2(n) = \prod{p_i^{\lceil \frac{a_i}{2} \rceil}}

g 3 ( n ) = p i a i 3 g_3(n) = \prod{p_i^{\lceil \frac{a_i}{3} \rceil}}

那么 G ( n ) = [ A 1 ] [ B g 2 ( n ) ] [ C g 3 ( n ) ] G(n) = [\frac{A}{1}][\frac{B}{g_2(n)}][\frac{C}{g_3(n)}]

这样的话式子 = t = 1 G ( t ) F ( t ) = \sum_{t=1}G(t)F(t) 中的 G ( t ) G(t) F ( t ) F(t) 均可在 O ( n ) O(n) 线性时间复杂度内预处理出来.

另外:这道题卡常数,真的恶心.

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll MOD = 1<<30;
const int maxn = 10000000;
int zhi[maxn+10],prime[maxn+10],pcnt,F[maxn+10];
int low[maxn+10],lowm[maxn+10],g2[maxn+10],g3[maxn+10];
void sieve(){
    zhi[1] = 1;
    low[1] = F[1] = g3[1] = g2[1] = 1;
    lowm[1] = 0;
    for(int i = 2;i <= maxn;++i){
        if(!zhi[i]) {
            F[i] = i-2,prime[pcnt++] = i;
            low[i] = i;lowm[i] = 1;
            g3[i] = g2[i] = i;
        }
        for(int j = 0;j < pcnt && prime[j]*i <= maxn;++j){
            zhi[i*prime[j]] = 1;
            if(i % prime[j] == 0){
                low[i*prime[j]] = low[i] * prime[j];
                lowm[i*prime[j]] = lowm[i]+1;
                if(i == low[i]){
                    if(i == prime[j]) F[i*prime[j]] = prime[j]*prime[j]-2*prime[j]+1;
                    else F[i*prime[j]] = F[i] * prime[j];
                }
                else
                {
                    F[i*prime[j]] = F[i/low[i]]*F[low[i]*prime[j]];
                }
                g2[i*prime[j]] = g2[i];
                g3[i*prime[j]] = g3[i];
                if(lowm[i*prime[j]] % 2 == 1) g2[i*prime[j]] *= prime[j];
                if(lowm[i*prime[j]] % 3 == 1) g3[i*prime[j]] *= prime[j];
                break;
            }
            else
            {
                F[i*prime[j]] = F[i] * F[prime[j]];
                low[i*prime[j]] = prime[j];
                lowm[i*prime[j]] = 1;
                g2[i*prime[j]] = g2[i] * prime[j];
                g3[i*prime[j]] = g3[i] * prime[j];
            }
        }
    }
}

int T ,A,B,C;
int main(){
    sieve();
    cin>>T;
    while(T--){
        scanf("%d%d%d",&A,&B,&C);
        ll ans = 0;
        for(int n = 1;n <= max(A,max(B,C));++n){
            ans = (ans + (A/n)*(B/g2[n])%MOD*(C/g3[n])%MOD*F[n]%MOD) % MOD;
        }
        cout<<ans<<endl;
    }
    return 0;
}

BZOJ4559 [成绩比较]

知识清单

  1. 动态规划
  2. 拉格朗日插值

题解

分两部分来解决:

1.先不考虑具体分数,只考虑满足相对排名计算得到方案数.

定义 d p [ i ] [ j ] dp[i][j] 表示仅考虑前 i i 节课,B神恰好碾压 j j 个人.
转移方法:
显然 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j + k ] dp[i][j+k] 转移过来.
具体转移方法:
考虑第 i i 门课,从 j + k j+k 中选出 k k 个,他们的分数大于B神,剩下 j j 个放在B神的下面,表示继续被碾压.方案总数 C k + j k C_{k+j}^{k} .
剩下的还有 N 1 ( j + k ) N-1-(j+k) 名同学,这些同学中取出 R a n k [ i ] k Rank[i]-k 名同学放在B神上面,填满 R a n k [ i ] Rank[i] .方案总数 C N 1 j k R a n k [ i ] k C_{N-1-j-k}^{Rank[i]-k} .
因此转移方程:
d p [ i ] [ j ] = k = 0 k + j N 1 d p [ i ] [ j + k ] C k + j k C N 1 j k R a n k [ i ] k dp[i][j] = \sum_{k=0}^{k+j≤N-1}{dp[i][j+k]C_{k+j}^{k}}C_{N-1-j-k}^{Rank[i]-k}
dp初始化:
d p [ 0 ] [ N 1 ] = 1 dp[0][N-1] = 1

该部分具体实现代码:

// dp[i][j]表示仅考虑前i门课程,其中有j名同学被B神碾压,的相对排名的方案数.  
dp[0][N-1] = 1;
for(int i = 1;i <= M;++i){
    for(int j = N-1;j >= 0;--j){
        for(int k = 0;k+j<=N-1;++k){
            ll res = C[j+k][k] * C[N-1-(j+k)][R[i]-1-k] % MOD;
            res = res * dp[i-1][j+k] % MOD;
            dp[i][j] = (dp[i][j] + res) % MOD;
        }
    }
}

2.求出满足题目给出的排名条件的分数具体分配方案数.

对于第 i i 门课,设B神的排名为 R a n k [ i ] Rank[i] ,则该门课产生的方案数就是枚举该门课B神的分数得到的方案数之和:
L R a n k [ i ] [ U x ] = t = 1 U x t N R a n k [ i ] ( U x t ) R a n k [ i ] 1 L_{Rank[i]}[U_x] = \sum_{t=1}^{Ux}{t^{N-Rank[i]}(U_x-t)^{Rank[i]-1}}
这个式子是关于 U x U_x N N 次多项式,而 U x Ux 最大则可能达到 1 e 9 1e9 ,显然不可能直接来求,我们考虑拉格朗日插值.


L a g r a n g e Lagrange 插值简介

对于一个 n n 次的多项式,只需要 n + 1 n+1 个点即可唯一确定,而用 n + 1 n+1 个点恢复这个 n n 次多项式的方法就是 L a g r a n g e Lagrange 插值法.

( x i , y i ) (x_i,y_i) 为我们已经获得的 N + 1 N+1 个点.

定义 f i [ x ] = i ! = j x x j x i x j f_i[x] = \sum_{i!=j}{\frac{x-x_j}{x_i-x_j}}

那么 L [ x ] = i = 1 N + 1 y i f i [ x ] L[x] = \sum_{i=1}^{N+1}{y_if_i[x]}


如果这道题用 L a g r a n g e Lagrange 插值求得了 L [ x ] L[x] ,那么第 i i 门课的答案即是 L R a n k [ i ] [ U x ] L_{Rank[i]}[U_x] .

//拉格朗日实现具体代码
ll Lagrange(int id){
    //拉格朗日插值
    for(int xi = 1;xi <= N+1;++xi){
        x[xi] = xi;
        y[xi] = 0;
        for(int i = 1;i <= xi;++i){
            y[xi] = (y[xi] + (mod_pow(i,N-R[id])*mod_pow(xi-i,R[id]-1)%MOD)) % MOD;
        }
    }

    ll ans = 0;
    for(int xi = 1;xi <= N+1;++xi){
        ll res = y[xi];
        for(int xj = 1;xj <= N+1;++xj){
            if(xj == xi) continue;
            res = res * (U[id] - xj + MOD) % MOD;
            res = res * div(xi - xj + MOD) % MOD;
        }
        ans = (ans + res) % MOD;
    }
    return ans;
}

综合起来,这道题的答案就是 a n s = d p [ M ] [ K ] i = 1 M L R a n k [ i ] [ U x ] ans = dp[M][K]*\prod_{i=1}^{M}{L_{Rank[i]}[U_x]}

###实现代码

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

using namespace std;

#define pr(x) cout<<#x<<":"<<x<<endl
typedef long long ll;
int N,M,K;
const ll MOD = 1e9+7;
ll mod_pow(ll x,ll n){
    if(n == 0) return 1;
    ll res = 1;
    while(n){
        if(n&1) res = res * x %MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}
const int maxn = 107;
ll R[maxn],U[maxn],dp[maxn][maxn];
ll C[maxn][maxn];
ll div(ll x){
    return mod_pow(x,MOD-2);
}
void initC(){
    C[0][0] = 1;
    for(int i = 1;i < maxn;++i){
        C[i][0] = 1;
        for(int j = 1;j <= i;++j){
            C[i][j] = (C[i-1][j] + C[i-1][j-1])%MOD;
        }
    }
}
ll x[maxn],y[maxn];
ll Lagrange(int id){
    //拉格朗日插值
    for(int xi = 1;xi <= N+1;++xi){
        x[xi] = xi;
        y[xi] = 0;
        for(int i = 1;i <= xi;++i){
            y[xi] = (y[xi] + (mod_pow(i,N-R[id])*mod_pow(xi-i,R[id]-1)%MOD)) % MOD;
        }
    }

    ll ans = 0;
    for(int xi = 1;xi <= N+1;++xi){
        ll res = y[xi];
        for(int xj = 1;xj <= N+1;++xj){
            if(xj == xi) continue;
            res = res * (U[id] - xj + MOD) % MOD;
            res = res * div(xi - xj + MOD) % MOD;
        }
        ans = (ans + res) % MOD;
    }
    return ans;
}
int main(){
    initC();
    ios::sync_with_stdio(false);
    cin>>N>>M>>K;
    for(int i = 1;i <= M;++i) cin>>U[i];
    for(int i = 1;i <= M;++i) cin>>R[i];

    // dp[i][j]表示仅考虑前i门课程,其中有j名同学被B神碾压,的相对排名的方案数.
    
    dp[0][N-1] = 1;
    for(int i = 1;i <= M;++i){
        for(int j = N-1;j >= 0;--j){
            for(int k = 0;k+j<=N-1;++k){
                ll res = C[j+k][k] * C[N-1-(j+k)][R[i]-1-k] % MOD;
                res = res * dp[i-1][j+k] % MOD;
                dp[i][j] = (dp[i][j] + res) % MOD;
            }
        }
    }

    ll ans = dp[M][K];

    for(int i = 1;i <= M;++i){
        ans = ans * Lagrange(i) % MOD;
    }
    cout<<ans<<endl;
    return 0;
}

BZOJ2633 [已经没有什么好害怕的了]

知识清单

  1. 容斥原理
  2. 动态规划

题意

给出 n n 个数 a i a_i n n 个数 b i b_i ,将 a i a_i b i b_i 进行匹配,使得匹配中 a i &gt; b i a_i&gt;b_i 的对数比 a i &lt; b i a_i&lt;b_i 的匹配数大 k k 组.

题解

首先将 a , b a,b 数组排序,然后对于每个 i i 求出满足 b j &lt; a i ( 1 j n ) b_j &lt; a_i (1≤j≤n) j j 的个数,并记为 c n t [ i ] cnt[i] .

定义 f [ i ] [ j ] f[i][j] 表示仅考虑前 i i a a 中,满足有 j j 对匹配使得 a u &gt; b v a_u &gt; b_v 成立的方案数.(显然 1 j i 1≤j≤i ) (注意这个定义中不要求剩下的 a a b b 进行匹配.)

那么我们可以得到转移方程:
f [ i ] [ j ] = f [ i 1 ] [ j ] + f [ i 1 ] [ j 1 ] ( c n t [ i ] ( j 1 ) ) f[i][j] = f[i-1][j] + f[i-1][j-1]*(cnt[i]-(j-1))

看起来似乎 f [ n ] [ j ] ( n j ) ! f[n][j]*(n-j)! 就是我们要找的答案?

但显然并不是,因为 ( n j ) ! (n-j)! 表示的剩下未匹配的元素进行随机搭配的时候,有时候也会搭配出满足 a u &gt; b v a_u &gt; b_v 的匹配,像这种就是不合法的,因此我们还要进行去重操作.

g [ i ] g[i] 表示在 n n 个匹配中,恰好有 i i 个匹配满足 a u &gt; b v a_u &gt; b_v 的方案数.显然这回 g [ ( n + k ) / 2 ] g[(n+k)/2] 就是我们要的答案.

容易想到转移方程:
g [ i ] = f [ n ] [ i ] ( n i ) ! g [ i + 1 ] C i + 1 i g [ i + 2 ] C I + 2 i . . . g [ n ] C n i g[i]=f[n][i]*(n-i)!-g[i+1]*C^{i}_{i+1}-g[i+2]*C^{i}_{I+2}-...-g[n]*C^{i}_{n}

解释一下为什么要乘以 C j i C^{i}_{j} 这个系数:因为 f [ n ] [ i ] ( n i ) ! f[n][i]*(n-i)! 产生的满足有 j j a u &gt; b v a_u &gt; b_v 的匹配数的方案数可以由固定 i i 个匹配数来得到(剩下的 j i j-i 个匹配由 ( j i ) ! (j-i)! 部分负责).而固定 i i 个匹配数的方案数刚好就是 C j i C^{i}_{j}

得到这个式子倒着转移就可以了.

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

using namespace std;
const ll MOD = 1e9+9;
int n,k;
const int maxn = 2007;
ll f[maxn][maxn],g[maxn];
ll a[maxn],b[maxn];
ll fac[maxn];
ll C[maxn][maxn];
ll cnt[maxn];
int main(){
    C[0][0] = fac[0] = 1;
    for(int i = 1;i <= 2000;++i) {
        fac[i] = fac[i-1] * i % MOD;
        C[i][0] = 1;
    }
    for(int i = 1;i <= 2000;++i) {
        for(int j = 1;j <= i;++j){
            C[i][j] = (C[i-1][j] + C[i-1][j-1]) % MOD;
        }
    }
    cin>>n>>k;
    if((n+k)%2 != 0) return puts("0");
    for(int i = 1;i <= n;++i) scanf("%lld",&a[i]);
    for(int i = 1;i <= n;++i) scanf("%lld",&b[i]);
    sort(a+1,a+1+n);
    sort(b+1,b+1+n);
    for(int i = 1;i <= n;++i){
        cnt[i] = lower_bound(b+1,b+1+n,a[i])-b-1;
    }
    f[0][0] = 1;
    for(int i = 1;i <= n;++i){
        f[i][0] = 1;
        for(int j = 1;j <= i;++j){
            f[i][j] = f[i-1][j] + (f[i-1][j-1]*max(cnt[i]-j+1,0LL)%MOD);
            f[i][j] %= MOD;
        }
    }
    for(int i = n;i >= (n+k)/2;--i){
        g[i] = f[n][i]*fac[n-i]%MOD;
        for(int j = i+1;j <= n;++j){
            g[i] = (g[i] + MOD - (C[j][i] * g[j] % MOD)) % MOD;
        }
    }
    cout<<g[(n+k)/2]<<endl;
    return 0;
}

BZOJ3930 [选数]

知识清单

  1. 容斥原理 (或莫比乌斯反演)

题解

题目给的一个很关键的信息就是 H L 1 e 5 H-L ≤ 1e5 .这意味着如果在区间 [ L , H ] [L,H] 中选出 N N 个不完全相同的整数,那么他们的 G C D H L 1 e 5 GCD≤H-L≤1e5
证明: G C D ( x , y ) = G C D ( x y , y ) &lt; = x y GCD(x,y) = GCD(x-y,y) &lt;= x-y

如果将 [ L , H ] [L,H] 中的各个元素除以 K K ,就相当于在 ( L 1 K , H K ] (\frac{L-1}{K},\frac{H}{K}] 中找 N N 个不完全相同的数使得其 G C D = 1 GCD = 1

F [ x ] F[x] 表示满足在 ( L 1 K , H K ] (\frac{L-1}{K},\frac{H}{K}] 中找 N N 个不完全相同的元素,使得其 G C D = 1 GCD=1 的方案数.

显然 F [ x ] = ( [ H K x ] [ L 1 K x ] ) N ( [ H K x ] [ L 1 K x ] ) F [ 2 x ] F [ 3 x ] . . . . F[x] = ([\frac{H}{Kx}]-[\frac{L-1}{Kx}])^N - ([\frac{H}{Kx}]-[\frac{L-1}{Kx}]) - F[2*x] - F[3*x] -....

遇见这种递推式子直接倒着进行转移就可以了.
注意,如果 K K 存在于区间 [ L , H ] [L,H] 之间,那么 a n s + + ans++ ,因为这种属于 N N 个数字全部相等的情况,我们的算法是没有考虑进去的.

另外,
这题实际上可以使用莫比乌斯反演,而且莫比乌斯反演形式特别直接,但是复杂度不对.

重新定义 F [ x ] F[x] 为在 [ L , H ] [L,H] 中找 N N 个数使得其 G C D = X GCD=X 的方案数
我们可以得到
n d F [ d ] = ( [ H n ] [ L 1 n ] ) N \sum_{n|d}{F[d]} = ([\frac{H}{n}]-[\frac{L-1}{n}])^N
反演得到,
F [ n ] = n d μ ( d n ) ( [ H d ] [ L 1 d ] ) N F[n] = \sum_{n|d}{\mu(\frac{d}{n})([\frac{H}{d}]-[\frac{L-1}{d}])^N}

我看到其他的博客有讲到分块的方法,但感觉复杂度有点玄学.

const ll MOD = 1e9+7;
ll N,K,L,R;
ll mod_pow(ll x,ll n){
    ll res = 1;
    while(n){
        if(n & 1) res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}
ll F[200007];
const int maxn = 200000;
int main(){
    cin>>N>>K>>L>>R;
    ll ans = K >= L && K <= R;
    R /= K;
    L --;
    L /= K;
    ll len = R - L;
    for(ll d = len;d >= 1;--d){
        ll res = R/d - L/d;
        F[d] = (mod_pow(res,N)-res+MOD) % MOD;
        for(ll k = 2*d;k <= len;k += d)
            F[d] = (F[d] - F[k] + MOD) % MOD;
    }
    cout<<ans+F[1]<<endl;
    return 0;
}

徐州网络赛 Easy Math

前置技能

  1. 容斥原理
  2. 线性筛
  3. 杜教筛

题解

i = 1 m μ ( i n ) \sum_{i=1}^{m}\mu(in)

n n 存在平方因子的时候,直接输出 0 0 作为答案,这是很显然的.

否则, n = p 1 p 2 . . . p k n = p_1p_2...p_k ,不含平方因子.

考虑区间 [ 1 , m ] [1,m] 中的数字 i i ,如果 i i 不含有因子 p 1 p_1 ,则可以使用积性函数的性质.

即: μ ( i n ) = μ ( i n p 1 p 1 ) = μ ( i n p 1 ) \mu(i*n) = \mu(i*\frac{n}{p_1}*p_1) = -\mu(i*\frac{n}{p_1})

而区间 [ 1 , m ] [1,m] 中的数并不全都与 p 1 p_1 互质,这就会造成 μ ( i n ) = 0 \mu(i*n) = 0 μ ( i n p 1 ) 0 \mu(i*\frac{n}{p_1}) \ne 0 的情况,这就要求我们用容斥的思想来去掉这部分.

如果 i i 中恰好有一个因子 p 1 p_1 ,则 μ ( i n p 1 ) = μ ( i p 1 n ) \mu(i*\frac{n}{p_1}) = \mu(\frac{i}{p_1}*n)

因此要减去 i = 1 n p 1 μ ( i n ) \sum_{i=1}^{\lfloor \frac{n}{p_1} \rfloor} \mu(in)

而如果 i i 中有多于 1 1 个的 p i p_i 因子,那么这部分的莫比乌斯函数值无论是在那部分中都是 0 0 ,因此不会对最终答案造成影响.

这样相当于降低了问题的规模.

我们可以列出递推式子,然后递推解决这个问题.

i = 1 n μ ( i n ) = i = 1 n μ ( i n p 1 ) + i = 1 n p 1 μ ( i n ) \sum_{i=1}^{n}{\mu(in)} = -\sum_{i=1}^{n}{\mu(i*\frac{n}{p_1})} + \sum_{i=1}^{\lfloor \frac{n}{p_1} \rfloor} \mu(in)

递推终止的条件是

  1. n = = 1 n == 1 时候,使用杜教筛得到莫比乌斯函数的前缀和
  2. \sum 的上限为 0 0 时候,直接返回 0 0 .
typedef long long ll;
const int N = 10000000;;
int mu[N+10],prime[N+10],pcnt,zhi[N+10];
void sieve(){
    pcnt = 0;
    mu[1] = zhi[1] = 1;
    for(int i = 2;i <= N;++i){
        if(!zhi[i]) mu[i] = -1,prime[pcnt++] = i;
        for(int j = 0;j < pcnt && prime[j]*i <= N;++j){
            zhi[i*prime[j]] = 1;
            if(i % prime[j] == 0){
                mu[i*prime[j]] = 0;
                break;
            }
            else{
                mu[i*prime[j]] = -mu[i];
            }
        }
    }
    for(int i = 1;i <= N;++i) mu[i] += mu[i-1];
}
map<ll,int> rec,vis;
int Mu(int x){
    if(x <= N) return mu[x];
    if(vis[x]) return rec[x];
    int res = 1,now = x,nxt;
    while(now >= 2){
        nxt = x/(x/now+1);
        res -= (now - nxt) * Mu(x/now);
        now = nxt;
    }
    vis[x] = 1;
    return rec[x] = res;
}
ll m,n;
ll np[20];int npcnt;
int Ans(ll u,ll d){
    if(d == 1) return Mu(u);
    if(u == 0) return 0;
    for(int i = 0;i < npcnt;++i){
        if(d % np[i] == 0){
            return -Ans(u,d/np[i]) + Ans(u/np[i],d);
        }
    }
}
bool divide(){
    ll x = n;
    for(ll i = 2;i * i <= n;++i){
        int cc = 0;
        if(x % i == 0) np[npcnt++] = i;
        while(x % i == 0) x /= i,cc++;
        if(cc >= 2) return false;
    }
    if(x > 1) np[npcnt++] = x;
    return true;
}
int main(){
    sieve();
    cin>>m>>n;
    if(!divide()) return puts("0");
    cout<<Ans(m,n)<<endl;
    return 0;
}

NWERC2015 Debugging

题目描述

有一份包含一个Bug的 n , ( 1 n 1 0 6 ) n,(1 \le n \le 10^6 ) 行代码,运行一次到崩溃的时间为 r , 1 r 1 0 9 r,1 \le r \le 10^9 .
现在你可以在任意一行花费时间 p , 1 p 1 0 9 p,1\le p \le 10^9 设置一个 p r i n t f printf 语句来判断程序是否运行到这里.
请问在最坏情况下,最少需要多少时间可以定位到bug所在行.

题解

因为我们调试的时候往往会运行很多次程序,而每次运行完之后,我们都能定位bug在哪一块代码行中.

因此我们动态规划的思想得到递推公式.
d p [ n ] dp[n] 表示调试 n n 行代码所需要花的时间.

d p [ n ] = m i n 1 k n ( d p [ n k + 1 ] + k p + r ) dp[n] = min_{1 \le k \le n}(dp[\frac{n}{k+1}]+k*p + r)

直接递推的时间复杂度是 O ( n 2 ) O(n^2) 的,而如果我们发现对于 [ n k + 1 ] [\frac{n}{k+1}] 相等的 k k ,我们只取 k k 的最小值就可以了.

所以通过底数分块,我们可以求得转移的时间复杂度为 n \sqrt{n} ,而真正有效的状态数也只有 n \sqrt{n} ,所以时间复杂度上限不会超过 O ( n ) O(n) .

猜你喜欢

转载自blog.csdn.net/weixin_37517391/article/details/82718970
今日推荐