• 2022.8.8好题选讲(数论场)


    T1 反素数

      传送门:[POI2001][HAOI2007] 反素数

    t a g tag tag暴力 dfs ???

    分析

      我们首先没有思路,于是考虑拿出一个数 n n n 对她进行质因数分解:

    n = ∏ p i k i n = \prod_{}p_i^{k_i} n=piki

      再经过一些观察可以发现,如果对于 p i p_i pi 从小到大排序过后,对于一个反素数而言, k i k_i ki 是单调不增的。

      这个其实挺好证明的,因为如果 k i k_i ki 单调递增了( k i > k i − 1 k_i > k_{i - 1} ki>ki1),那么我们交换 k i k_i ki k i − 1 k_{i - 1} ki1,那么我们就得到了一个因数个数与原数相同的且值比原数小的数 n ′ n' n,这样一来 n n n 就不满足反素数的定义了,证毕。

      于是我们可以得出一个性质,那么就是 1 e 9 1e9 1e9 反素数一定由前 10 10 10 个质数组成(这个很显然吧)。那么我们就可以愉快的 d f s dfs dfs 了,就是尝试确定每一个质数的指数,同时记录一个约数个数就好了。

    代码

    #include
    using namespace std;
    #define in 
    
    int n = 0;
    int mx = 0;
    int ans = 0;
    int p[11] = { 0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 19 };
    
    void dfs(int m, int ind, int num, int k){               // 当前数 当前质数编号 当前约数个数 当前指数 
    	if(num > mx or (num == mx and m < ans)) ans = m, mx = num;
    	int j = 0, temp, i = m;
    	while(j < k){
    		j++;
    		if(n / i < p[ind]) break;
    		temp = num * (j + 1), i = i * p[ind];
    		if(i <= n) dfs(i, ind + 1, temp, j);
    	}
    }
    
    signed main(){
    	cin >> n;
    	dfs(1, 1, 1, 30);
    	cout << ans << '\n';
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    T2 余数求和

      传送门:[CQOI2007]余数求和

    t a g tag tag模运算转加减乘除???

    分析

      直接推式子:

    ∑ i = 1 n k m o d    i = ∑ i = 1 n k − i × ⌊ k i ⌋ = n k − ∑ i = 1 n i × ⌊ k i ⌋

    i=1nkmodi=i=1nki×ki=nki=1ni×ki" role="presentation" style="position: relative;">i=1nkmodi=i=1nki×ki=nki=1ni×ki
    ==i=1nkmodii=1nki×iknki=1ni×ik

      然后我们发现后面那一坨: ∑ i = 1 n i × ⌊ k i ⌋ \sum\limits_{i = 1}^n i \times \lfloor \frac ki \rfloor i=1ni×ik 可以整除分块,然后就可以 O ( n ) O(\sqrt n) O(n ) 做完了。

    代码

    #include
    using namespace std;
    #define int long long
    
    int ans = 0;
    int n = 0; int k = 0;
    
    signed main(){
    	cin >> n >> k;
    	ans += n * k;
    	for(int l = 1, r; l <= n; l = r + 1){
    		if(k / l != 0) r = min(n, k / (k / l));
    		else r = n;
    		ans -= (k / l) * (l + r) * (r - l + 1) / 2;
    	}
    	cout << ans << '\n';
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    T3 yy 的 gcd

      传送门:yy 的 gcd

    t a g tag tag莫比乌斯反演???

    分析

      求得答案就是( p r i m e prime prime 表示质数集):

    a n s = ∑ i = 1 n ∑ j = 1 m [ gcd ⁡ ( i , j ) ∈ p r i m e ] ans = \sum_{i = 1}^n \sum_{j = 1}^m [\gcd(i, j) \in prime] ans=i=1nj=1m[gcd(i,j)prime]

      我们把他化简成能莫反的形式:

    a n s = ∑ k ∈ p r i m e ∑ i = 1 n ∑ j = 1 m [ gcd ⁡ ( i , j ) = k ] = ∑ k = ∈ p r i m e ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ m k ⌋ [ gcd ⁡ ( i , j ) = 1 ]

    ans=kprimei=1nj=1m[gcd(i,j)=k]=k=∈primei=1nkj=1mk[gcd(i,j)=1]" role="presentation" style="position: relative;">ans=kprimei=1nj=1m[gcd(i,j)=k]=k=∈primei=1nkj=1mk[gcd(i,j)=1]
    ans==kprimei=1nj=1m[gcd(i,j)=k]k=∈primei=1knj=1km[gcd(i,j)=1]

      然后就是喜闻乐见的莫反环节了:

    = ∑ k = ∈ p r i m e ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ m k ⌋ ∑ d ∣ gcd ⁡ ( i , j ) μ ( d )

    =k=∈primei=1nkj=1mkd|gcd(i,j)μ(d)" role="presentation" style="position: relative;">=k=∈primei=1nkj=1mkd|gcd(i,j)μ(d)
    =k=∈primei=1knj=1kmdgcd(i,j)μ(d)

      再然后就是一些小 t r i c k trick trick 了:

    = ∑ k = ∈ p r i m e ∑ d = 1 ⌊ n k ⌋ ∑ i = 1 ⌊ n k ⌋ ∑ j = 1 ⌊ m k ⌋ [ d ∣ i ∧ d ∣ j ] μ ( d ) = ∑ k = ∈ p r i m e ∑ d = 1 ⌊ n k ⌋ ⌊ n k d ⌋ ⌊ m k d ⌋ μ ( d )

    =k=∈primed=1nki=1nkj=1mk[d|id|j]μ(d)=k=∈primed=1nknkdmkdμ(d)" role="presentation" style="position: relative;">=k=∈primed=1nki=1nkj=1mk[d|id|j]μ(d)=k=∈primed=1nknkdmkdμ(d)
    ==k=∈primed=1kni=1knj=1km[didj]μ(d)k=∈primed=1knkdnkdmμ(d)

      然后我们发现好像式子已经化到最简了,但是我们算一下复杂度就会发现我们愉快的 T T T 掉了。所以说这里又有一个常用的降复杂度的 t r i c k trick trick:令 T = k d T = kd T=kd,然后:

    a n s = ∑ k = ∈ p r i m e ∑ d = 1 ⌊ n k ⌋ ⌊ n T ⌋ ⌊ m T ⌋ μ ( d )

    ans=k=∈primed=1nknTmTμ(d)" role="presentation" style="position: relative;">ans=k=∈primed=1nknTmTμ(d)
    ans=k=∈primed=1knTnTmμ(d)

      然后考虑枚举 T T T,并把 T T T 有关的提到前面去:

    = ∑ T = 1 n ⌊ n T ⌋ ⌊ m T ⌋ ∑ k ∣ T ∧ k ∈ p r i m e μ ( T k ) = \sum_{T = 1}^n \left\lfloor \frac{n}{T} \right\rfloor \left\lfloor \frac{m}{T} \right\rfloor \sum_{k | T \land k \in prime} \mu(\frac Tk) =T=1nTnTmkTkprimeμ(kT)

      然后我们发现后面 ∑ k ∣ T ∧ k ∈ p r i m e μ ( T k ) \sum\limits_{k | T \land k \in prime} \mu(\frac Tk) kTkprimeμ(kT) 可以预处理,具体来说对于每一个质数 k k k 枚举 k k k 的倍数 T T T,然后把她的值加上 μ ( T k ) \mu(\frac Tk) μ(kT) 就好了。

    代码

    #include
    using namespace std;
    #define in read() 
    #define MAXN 10000010
    
    inline int read(){
    	int x = 0; char c = getchar();
    	while(c < '0' or c > '9') c = getchar();
    	while('0' <= c and c <= '9'){
    		x = x * 10 + c - '0'; c = getchar();
    	}
    	return x;
    }
    
    int Q = 0;
    int n = 0; int m = 0;
    
    int cnt = 0;
    int flag[MAXN] = { 0 };
    int mu[MAXN] = { 0 };
    int prime[MAXN] = { 0 };
    long long f[MAXN] = { 0 };
    long long sum[MAXN] = { 0 };
    void sieve(){
    	mu[1] = 1;
    	for(int i = 2; i <= MAXN; i++){
    		if(!flag[i]) prime[++cnt] = i, mu[i] = -1;
    		for(int j = 1; j <= cnt and i * prime[j] <= MAXN; j++){
    			flag[i * prime[j]] = 1;
    			if(i % prime[j] == 0) break;
    			mu[i * prime[j]] = -mu[i];
    		}
    	}
    	for (int i = 1; i <= cnt; i++)
            for (int j = 1; prime[i] * j <= MAXN; j++)
                f[j * prime[i]] += mu[j];
        for (int i = 1; i <= MAXN; i++)
            sum[i] = sum[i - 1] + f[i];
    }
    
    long long solve(int a,int b) {
        long long ans = 0;
        if (a > b) swap(a, b);
        for (int l = 1, r; l <= a; l = r + 1) {
            r = min(a / (a / l), b / (b / l));
            ans += (sum[r] - sum[l - 1]) * (a / l) * (b / l);
        }
        return ans;
    }
    
    signed main(){
    	Q = in;
    	sieve();
    	while(Q--){
    		n = in; m = in;
    		if(n > m) swap(n, m);
    		cout << solve(n, m) << '\n';
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    T4 dkw 的 lcm

      传送门:dkw 的 lcm

    t a g tag tag把数看成希尔伯特空间???

    分析

      直接把式子告诉你了:

    a n s = ∏ i 1 = 1 n ∏ i 2 = 1 n ⋯ ∏ i k = 1 n φ ( l c m ( i 1 , i 2 , i 3 , ⋯   , i k ) ) m o d    ( 1 e 9 + 7 ) ans = \prod_{i_1 = 1}^n\prod_{i_2 = 1}^n \cdots \prod_{i_k = 1}^n \varphi(lcm(i_1, i_2, i_3, \cdots, i_k)) \mod (1e9+7) ans=i1=1ni2=1nik=1nφ(lcm(i1,i2,i3,,ik))mod(1e9+7)

      首先 t a g tag tag 里面的希尔伯特空间是个啥?简单来说无限维的空间就可以理解成一个希尔伯特空间。那么这个空间是怎么定义的呢?对于一个数 n n n 对他质因数分解,然后希尔伯特空间中的维度就代表质因子的次数。

      于是每一个数字 n n n 就能被看成希尔伯特空间中的一个向量。

      这样做的好处是什么呢?因为这样一来我们复杂的 l c m lcm lcm 操作就变成了对于很多的无限维向量,在每一个维度上取一个最大值,得到的新的数就是这些 n n n 维向量所代表的数的 l c m lcm lcm

      根据这个思想,我们就很容易想到把质因子单独拎出来考虑贡献(而且 φ ( n ) \varphi(n) φ(n) 还是积性函数)。所以我们直接考虑每一个 φ ( p t ) \varphi(p^t) φ(pt) 在最终的答案中出现的次数。然后这个就考虑容斥一下嘛,定义一下三个集合:

    1. A = { x ∣ p t ∤ x , x ≤ n } A = \{ x \mid p^t \nmid x, x \leq n \} A={xptx,xn} 也就是 1 ∼ n 1 \sim n 1n 中不是 p t p^t pt 的倍数的数的集合
    2. B = { x ∣ p t ∣ x , p t + 1 ∤ x , x ≤ n } B = \{ x \mid p^t \mid x, p^{t + 1} \nmid x, x \leq n \} B={xptx,pt+1x,xn} 也就是 1 ∼ n 1 \sim n 1n 中刚好是 p t p^t pt 的倍数的集合
    3. C = { x ∣ p t + 1 ∣ x , x ≤ n } C = \{ x \mid p^{t + 1} \mid x, x \leq n \} C={xpt+1x,xn} 也就是超过了 p t p^t pt

      那么 φ ( p t ) \varphi(p^t) φ(pt) 出现的次数就是:

    ( ∣ A ∣ + ∣ B ∣ ) k − ∣ A ∣ k (|A| + |B|)^k - |A|^k (A+B)kAk

      然后我们考虑 ∣ A ∣ + ∣ B ∣ |A| + |B| A+B 等于多少,首先显然等于 n − ∣ C ∣ n - |C| nC,那么我们考虑 ∣ C ∣ |C| C 是多少。这个也就很显然了嘛,因为 C C C 中只有 p t + 1 p^{t + 1} pt+1 的倍数,所以个数就是 ⌊ n p t + 1 ⌋ \left\lfloor\frac n{p^{t + 1}}\right\rfloor pt+1n

      那么同理,我们就可以得到:

    ∣ A ∣ = n − ⌊ n p t ⌋ ∣ B ∣ = ⌊ n p t ⌋ − ⌊ n p t + 1 ⌋ ∣ C ∣ = ⌊ n p t + 1 ⌋

    |A|=nnpt|B|=nptnpt+1|C|=npt+1" role="presentation" style="position: relative;">|A|=nnpt|B|=nptnpt+1|C|=npt+1
    A=B=C=nptnptnpt+1npt+1n

      然后这里有一个细节,就是因为 ( ∣ A ∣ + ∣ B ∣ ) k − ∣ A ∣ k (|A| + |B|)^k - |A|^k (A+B)kAk 这个东西是写在指数上的,所以根据扩展欧拉定理,运算的时候是对 1 e 9 + 6 1e9 + 6 1e9+6 取模而不是对 1 e 9 + 7 1e9 + 7 1e9+7 取模。

      于是我们枚举 p p p,然后枚举 t t t,然后计算 ( n − ⌊ n p t + 1 ⌋ ) k − ( n − ⌊ n p t ⌋ ) k \left(n - \left\lfloor\frac n{p^{t + 1}}\right\rfloor\right)^k - \left(n - \left\lfloor \frac n{p^t} \right\rfloor\right)^k (npt+1n)k(nptn)k,这个就快速幂 O ( log ⁡ k ) O(\log k) O(logk) 搞定。然后前面枚举的复杂度就是质数的小于等于 n n n 的 若干次方的个数也就是 O ( n ln ⁡ n ) O(\frac n{\ln n}) O(lnnn)(质数个数也是 O ( n ln ⁡ n ) O(\frac n{\ln n}) O(lnnn))。于是总的复杂度就是: O ( n ln ⁡ n log ⁡ k ) O(\frac n{\ln n}\log k) O(lnnnlogk) 的。

    T5 lcm sum

      传送门:SP5971 LCMSUM - LCM Sum

    t a g tag tag还是莫反???

    分析

      就直接推式子呗:

    a n s = ∑ i = 1 n l c m ( i , n ) = ∑ i = 1 n i n gcd ⁡ ( i , n ) = n ∑ i = 1 n i gcd ⁡ ( i , n ) = n ∑ d ∣ n ∑ i = 1 n i d [ d = gcd ⁡ ( i , n ) ]

    ans=i=1nlcm(i,n)=i=1ningcd(i,n)=ni=1nigcd(i,n)=ndni=1nid[d=gcd(i,n)]" role="presentation" style="position: relative;">ans=i=1nlcm(i,n)=i=1ningcd(i,n)=ni=1nigcd(i,n)=ndni=1nid[d=gcd(i,n)]
    ans====i=1nlcm(i,n)i=1ngcd(i,n)inni=1ngcd(i,n)indni=1ndi[d=gcd(i,n)]

      这里注意到满足 d = gcd ⁡ ( i , n ) d = \gcd(i, n) d=gcd(i,n) i i i 一定是 d d d 的倍数,所以这里我们考虑改为枚举 i d \frac i d di 的值:

    = n ∑ d ∣ n ∑ i d = 1 n d i d [ d = gcd ⁡ ( i , n ) ] = n ∑ d ∣ n ∑ i = 1 n d i [ d = gcd ⁡ ( i d , n ) ] = n ∑ d ∣ n ∑ i = 1 n d i [ gcd ⁡ ( i , n d ) = 1 ] = n ∑ d ∣ n ∑ i = 1 d i [ gcd ⁡ ( i , d ) = 1 ]

    =ndnid=1ndid[d=gcd(i,n)]=ndni=1ndi[d=gcd(id,n)]=ndni=1ndi[gcd(i,nd)=1]=ndni=1di[gcd(i,d)=1]" role="presentation" style="position: relative;">=ndnid=1ndid[d=gcd(i,n)]=ndni=1ndi[d=gcd(id,n)]=ndni=1ndi[gcd(i,nd)=1]=ndni=1di[gcd(i,d)=1]
    ====ndndi=1dndi[d=gcd(i,n)]ndni=1dni[d=gcd(id,n)]ndni=1dni[gcd(i,dn)=1]ndni=1di[gcd(i,d)=1]

      这里看着就很想莫反,但是仔细思考一下发现其实不用,这里面的这一坨:

    ∑ i = 1 d i [ gcd ⁡ ( i , d ) = 1 ] \sum_{i = 1}^d i[\gcd(i, d) = 1] i=1di[gcd(i,d)=1]

      这个东西的意义就是 [ 1 , d ] [1, d] [1,d] 中与 d d d 互质的数字的和,那么这一坨显然就和 φ ( d ) \varphi(d) φ(d) 有关,具体怎样有关呢。

      我们发现,如果 gcd ⁡ ( i , d ) = 1 \gcd(i, d) = 1 gcd(i,d)=1,那么就有 gcd ⁡ ( d − i , d ) = 1 \gcd(d - i, d) = 1 gcd(di,d)=1,所以与 d d d 互质的数成对出现并且两两和为 d d d,那么这一坨求和就显然是:

    ∑ i = 1 d i [ gcd ⁡ ( i , d ) = 1 ] = d φ ( d ) 2 \sum_{i = 1}^d i[\gcd(i, d) = 1] = \frac{d\varphi(d)}{2} i=1di[gcd(i,d)=1]=2dφ(d)

      那么总的式子就是:

    a n s = 1 2 n ∑ d ∣ n d φ ( d ) ans = \frac 12n\sum_{d \mid n} d\varphi(d) ans=21ndndφ(d)

      显然这个函数也是积性的,所以直接线性筛筛 1 e 6 1e6 1e6 就完事了。

    代码

    #include
    using namespace std;
    #define in read()
    #define MAXN 1001000
    
    inline int read(){
    	int x = 0; char c = getchar();
    	while(c < '0' or c > '9') c = getchar();
    	while('0' <= c and c <= '9'){
    		x = x * 10 + c - '0'; c = getchar();
    	}
    	return x;
    }
    
    int T = 0; int n = 0;
    int tot = 0;
    int v[MAXN] = { 0 };
    int p[MAXN] = { 0 };
    int phi[MAXN] = { 0 };
    int ans[MAXN] = { 0 };
    void sieve(int n){
    	phi[1] = 1;
    	for(int i = 2; i <= n; i++){
    		if(!v[i]) p[++tot] = i, phi[i] = i - 1;
    		for(int j = 1; j <= tot and i * p[j] <= n; j++){
    			v[j * p[j]] = 1;
    			if(i % p[j] == 0) { phi[i * p[j]] = phi[i] * p[j]; break; }
    			phi[i * p[j]] = phi[i] * (p[j] - 1);
    		}
    	}
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; i * j <= n; j++)
    			ans[i * j] += j * phi[j] / 2;
    	for(int i = 1; i <= n; i++) ans[i] = i * ans[i] + i;
    }
    
    int main(){
    	T = in;
    	sieve(1e6);
    	while(T--){
    		n = in;
    		cout << ans[n] << '\n';
    	}
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    lcm sum h

    数论

    数论之神

    屠龙勇士

    能量采集 noi 2010

  • 相关阅读:
    洛谷 P2852 [USACO06DEC]Milk Patterns G(后缀数组,height数组)
    在js中使用proxy的棘手问题
    外汇天眼:随着美元贬值所有世界货币,每个国家都在为自己着想
    Java IO 流及FILE
    服务网关GateWay基础
    【牛客面试必刷TOP101】Day8.BM33 二叉树的镜像和BM36 判断是不是平衡二叉树
    【spark】记录一次 spark sparkstreaming 使用命令行提交任务在Yarn集群模式无法正常消费kerberos kafka数据的问题
    java串口通信(基于串口协议开发的私有协议,实现传输字符串与文件)
    最频繁被问到的SQL面试题
    开源依赖管理的最佳实践
  • 原文地址:https://blog.csdn.net/ID246783/article/details/126227450