[kuangbin带你飞]专题十四 数论基础

A - Bi-shoe and Phi-shoe ——筛素数

题意
一个竹竿长度为p,它的score值就是比p长度小且与且与p互质的数字总数,比如9有1,2,4,5,7,8这六个数那它的score就是6。给你T组数据,每组n个学生,每个学生都有一个幸运数字,求出要求买 n n n个竹子每个竹子的score都要大于或等于该学生的幸运数字,每个竹竿长度就是花费,求最小花费。
思路
p p p长度小且与且与 p p p互质的数字总数就是欧拉函数的定义,就是找到第一个 a a a a a a的欧拉函数的值大于幸运值,仔细分析会发现,每次遇到质数的时候,欧拉函数就会变大,若 a a a是质数, a a a的欧拉函数就是 a − 1 a-1 a1,这也我们可以把问题转化为:在所有的素数中找到第一个 a a a a a a的欧拉函数 a − 1 a-1 a1比幸运值大,然后将所有的找到的 a a a的值 a 1 , a 2 , . . . a m a_1,a_2,...a_m a1,a2,...am求和。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int Max = 1e6+10;
ll luck;
ll f[Max],bk[Max];
vector<int> prime;
void init(){
    memset(bk,-1,sizeof(bk));
    for(int i = 2; i < Max; i++) f[i] = 1;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i ){
            f[j] = 0;
        }
    }
    for(int i = 2 ; i < Max; i++){
        if(f[i]) prime.push_back(i-1);
        if(bk[i-1] == -1)
            bk[i-1] = i;
    }

}
int main(){
    int n,cas;
    scanf("%d",&cas);
    init();
    for(int c = 1; c <= cas; c++){
        ll ans = 0;
         scanf("%d",&n);
         for(int i = 1; i <= n; i++){
            scanf("%d",&luck);
            int pos = lower_bound(prime.begin(),prime.end(),luck)-prime.begin();
            ans += bk[prime[pos]];
            //printf("%d",bk[prime[pos]]);
         }
         printf("Case %d: %lld Xukha\n",c,ans);
    }
    return 0;
}

B - Prime Independence

题意
给出n个数,找出一个最大素数独立子集,如果a=b*一个素数,那么认为a是b的一个素数乘级,如果一个集合不存在一个数是另一个数的素数乘级,那么这就是素数独立子集。
二分图匹配问题,不会

C - Aladdin and the Flying Carpet——算术基本定理的推论

题意
给一对数字 a,b ,a是一个长方形的面积,问有多少种整数的边的组合可以组成面积为a的长方形,要求最短的边不得小于b
思路
就是找到a有多少对因子满足xy = a,b<=x < y,同时有一个坑:x != y,有些博客给的解释是因为不可以是正方形。
利用N的正约数的个数为 ( c 1 + 1 ) × ( c 2 + 1 ) × . . . × ( c m + 1 ) {(c_1 + 1)\times(c_2+1)\times...\times(c_m+1)} (c1+1)×(c2+1)×...×(cm+1)
计算出所有的正约数个数 除以2得到数对的个数, 然后 减去 其中所有比b小的 x 的个数 和 x == y的个数。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#define ll long long
using namespace std;

const int Max = 1e6 + 10;
int f[Max];
vector<int> prime;
int cas;

void init(){
    for(int i = 2; i < Max; i++) f[i] = 1;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = 0;
        }
    }
    for(int i = 2 ; i < Max; i++){
        if(f[i]) prime.push_back(i);
    }
}
ll solve(ll n){
    ll ans = 1;
    int cur = 0;
    int len = prime.size();
    while(n > 1 && cur < len){
        int m= 0;
        while(n % prime[cur] == 0){
            n /= prime[cur];
            m++;
        }
        ans *= (m + 1);
        cur++;
    }
    //如果1e6内的质数没有可以整除的,
     //那么证明这是一个超过1e6的指数,m = 1;
    if(n!=1)ans *= 2;
   
    return ans;
}
int main(){
    init();
    scanf("%d",&cas);
    ll a,b;
    for(int c = 1; c <= cas; c++)
    {
        ll ans = 0;
        scanf("%lld %lld",&a,&b);
        if(b > sqrt(a)){
            ans = 0;
        }else{
            ll num = solve(a);

            ll cnt = 0;
            for(int i = 1; i < b; i++){
                if(a % i == 0) cnt++;
            }
            //printf("num = %lld  cnt = %lld\n",num,cnt);
            ans = num/2 - cnt;
        }
        printf("Case %d: %lld\n",c,ans);
    }
    return 0;
}
/*
10
16 2
10 2
12 2
18 3
*/

D - Sigma Function——算术基本定理

题意
n n n使用算术基本定理分解,
在这里插入图片描述
定义一个新的函数Sigma Function
在这里插入图片描述
给一个 n n n求出 1 ~ n 1~n 1n有多少个 σ ( k ) ( 1 < = k < = n ) \sigma(k)(1<= k<= n) σ(k)(1<=k<=n)的值是偶数。
思路
我们观察(p[i]^(e[i]+1) - 1)/(p[i] - 1) 这是一个等比数列和,那么可以将它变形为

p[i] ^ 0 + p[i] ^ 1 +…+ p[i] ^ e[i];

如果p[i] = 2; 则其和一定为奇数(因为从p[i] ^ 1开始后边都是偶数,而p[i] ^ 0是1 );

我们知道除2以外,素数都是奇数,所以 (p[i] ^ 0 + p[i] ^ 1 +…+ p[i] ^ e[i])要满足是奇数的话e[i]一定是偶数(两两组合还剩一个奇数);

这样如果Sigma(i)是奇数,那么连乘的每一项都是奇数。满足sigma(i)是奇数的条件,则如果i是一个平方数,那么 i 的约数和一定是奇数,如果2*i是平方数,它的约数和也是奇数,那么所有数 减去 其中Sigma(i)为奇数的个数 就是 Sigma(i)为偶数的个数。

//代码很简单,但思维却很难想
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>
#include<algorithm>

using namespace std;
typedef long long ll;
const int N = 1e6 + 10;

int main()
{
    int t, p = 0;
    ll n;
    scanf("%d", &t);
    while(t--)
    {
        p++;
        ll x, y;
        scanf("%lld", &n);
        x = (ll)sqrt(n);//计算n中x^2的个数
        y = (ll)sqrt(1.0 * n / 2);//计算n中2*x^2的个数
        printf("Case %d: %lld\n", p, n - x - y);
    }
    return 0;
}
E - Leading and Trailing——快速幂和对数

题意
给两个数 n n n k k k,求出 n k n^k nk的前三位数字和后三位数字。
思路
后三位很简单,直接快速幂 mod 一下1000,注意可能后三位可能前导0也要输出。
问题是前三位,这里有一个神奇的方法。
引入对数,将 n k n^k nk 取对数变为 t = k ∗ l o g ( n ) t = k*log(n) t=klogn,t是一个小数,将 t t t 的整数部分变成2,这样就可以将前三位变成整数,剩下的数都变成小数,然后直接强制取整就可以得到前三位的数了。

原理:因为任意一个数 m m m都可以表示成 1 0 q 10^q 10q的形式, q q q是一个小数, q q q的整数部分就是 m m m的数量级。
例如: m = 150 , l o g ( m ) = 2.1760912590556812420812890085306 m = 150,log(m) = 2.1760912590556812420812890085306 m=150log(m)=2.1760912590556812420812890085306
m m m的数量级就是100, l o g ( m ) log(m) log(m)的整数部分就是2,所以每次把 1 0 q 10^q 10q q q q的整数部分变成2,就可以把数量级降到100,这样前三位数一定属于整数部分,取整即可。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;

int cas;
ll n,k;
int ans_lead,ans_trail;
//快速幂
ll qpow(ll a, ll b, ll mod){
    ll res = 1;
    while(b){
        if(b & 1){
            res = res * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
int main(){
    scanf("%d",&cas);
    for(int c = 1; c <= cas; c++){
        scanf("%lld %lld",&n,&k);
        ans_trail = qpow(n,k,1000);
        double t = k*log10(1.0*n);//利用k*log(n)来转化
        int tt = floor(t);
        double solve = t - tt + 2;
        //double ans_lead = pow(10.0,fmod(k*log10(1.0*n),1));//前三位
        ans_lead = (ll)pow(10,solve);
        printf("Case %d: %3d %03d\n",c,ans_lead,ans_trail);
    }
    return 0;
}
/*
3
123456 2
*/

F - Goldbach`s Conjecture ——素数

题意:
给出几组测试数据,每组给出一个n,问n能被分成几对素数的和。
思路:
先打表,求出所有的素数。从小到大枚举所有素数,对于当前枚举的素数a,看n - a是否也是一个素数,如果是的话,就计数,求出答案。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int Max = 1e7 + 10;
bool f[Max];
int prime[666666];
int cas,n,len = 0;

void get_prime(){
    for(int i = 2; i < Max; i++) f[i] = 1;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = 0;
        }
    }
    for(int i = 2; i < Max; i++)
        if(f[i]) prime[len++] = i;
}
int main(){
    get_prime();
    scanf("%d",&cas);
    for(int c = 1; c <= cas; c++){
        int ans = 0;
        scanf("%d",&n);
        for(int i = 0; i < len; i++){
            if(prime[i] > n/2)
                break;
            if(f[n - prime[i]]){
                ans++;
            }
        }
        printf("Case %d: %d\n",c,ans);
    }
    return 0;
}

G - Harmonic Number (II)——规律 || 数论分块

题意
给出 n n n ,求和: n / 1 + n / 2 + n / 3 + . . . + n / n n/1 + n/2+n/3+...+n/n n/1+n/2+n/3+...+n/n
思路
n n n很大无法暴力。

一、找规律解法
没有想出解法 ,网上抄的规律
1.sqrt(n)之前的数我们可以直接用for循环来求
2.sqrt(n)之后的sum += (n/i - n/(i + 1)) * i;
当sqrt(n) = n / sqrt(n)时(如第一个例子10,sum就多加了一个3),sum多加了一个sqrt(n),减去即可;

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int cas;
ll n;
int main(){
    scanf("%d",&cas);
    for(int c = 1; c <= cas; c++){
        ll ans = 0;
        scanf("%lld",&n);
        int m = (int)sqrt(n);
        for(int i = 1; i <= m; i++)
            ans = ans + n / i;
        for(int i = 1; i <= m; i++){
            ans = ans + (n / i - n / (i + 1)) * i;
        }
        if(m == n / m) ans -= m;
        printf("Case %d: %lld\n",c,ans);
    }
    return 0;
}

仔细一想符合数论分块,打一下模板

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

int cas , icas = 1;
 ll n;
ll solve(ll n){
	ll res = 0;
	for(ll l  = 1, r; l <= n; l = r + 1){
		r = n / (n / l);
		res += (r - l + 1) * (n / l);
		//printf("l = %lld  r = %lld  ans = %lld\n",l,r,(r - l + 1) * (n / l));
	}
	return res;
}
int main(){
	scanf("%d",&cas);
	while(cas--){
		scanf("%lld",&n);
		printf("Case %d: %lld\n",icas++,solve(n));
	}	
	return 0;
}
H - Pairs Forming LCM——算术基本定理

题意
求出小于 n n n的数中,有多少数对的最小公倍数是 n n n
思路
由算术分解定理可以求出最大公约数和最小公倍数:
对于两个整数 a , b a,b a,b
a a a分解: a = p 1 e 1 p 2 e 2 p 3 e 3 . . . p m e m a=p_1^{e_1}p_2^{e_2}p_3^{e_3}...p_m^{e_m} a=p1e1p2e2p3e3...pmem

b b b分解: b = p 1 k 1 p 2 k 2 p 3 k 3 . . . p m k m b=p_1^{k_1}p_2^{k_2}p_3^{k_3}...p_m^{k_m} b=p1k1p2k2p3k3...pmkm

GCD( a , b a,b a,b)= p 1 m i n ( e 1 , k 1 ) p 2 m i n ( e 2 , k 2 ) p 3 m i n ( e 3 , k 3 ) . . . p m m i n ( e m , k m ) p_1^{min(e_1,k_1)}p_2^{min(e_2,k_2)}p_3^{min(e_3,k_3)}...p_m^{min(e_m,k_m)} p1min(e1,k1)p2min(e2,k2)p3min(e3,k3)...pmmin(em,km)

LCM( a , b a,b a,b)= p 1 m a x ( e 1 , k 1 ) p 2 m a x ( e 2 , k 2 ) p 3 m a x ( e 3 , k 3 ) . . . p m m a x ( e m , k m ) p_1^{max(e_1,k_1)}p_2^{max(e_2,k_2)}p_3^{max(e_3,k_3)}...p_m^{max(e_m,k_m)} p1max(e1,k1)p2max(e2,k2)p3max(e3,k3)...pmmax(em,km)

再将GCD( a , b a,b a,b)分解:GCD( a , b a,b a,b) = p 1 c 1 p 2 c 2 p 3 c 3 . . . p m c m =p_1^{c_1}p_2^{c_2}p_3^{c_3}...p_m^{c_m} =p1c1p2c2p3c3...pmcm

可知: c 1 = m a x ( e 1 , k 1 ) , c 2 = m a x ( e 2 , k 2 ) , . . . , c m = m a x ( e m , k m ) c_1 = max(e_1,k_1),c_2 = max(e_2,k_2),...,c_m = max(e_m,k_m) c1=max(e1,k1),c2=max(e2,k2),...,cm=max(em,km)

如果 e i = c i e_i = c_i ei=ci,那么 k i k_i ki 就可以取 [   0 , e i ] [ \ 0,e_i] [ 0,ei] e i + 1 e_i+1 ei+1种情况

同时,对称的来说,如果 k i = c i k_i = c_i ki=ci,那么 e i e_i ei 就可以取 [   0 , k i ] [ \ 0,k_i] [ 0,ki] k i + 1 k_i+1 ki+1种情况。

同时因为 k i = e i = c i k_i = e_i = c_i ki=ei=ci的情况被算了两次,所以要剪掉一次,这样一来第i项就一共有 2 ∗ c i + 1 2*c_i+1 2ci+1种情况了。

将每一项的情况相乘,就可以得出所有可能的情况,同时因为我们计算的时候是对称的,所以我们计算出的结果是真正结果的两倍。举个栗子:当 n = 4 n = 4 n=4的时候, a = 1 , b = 2 a=1,b = 2 a=1,b=2 a = 2 , b = 1 a= 2,b=1 a=2,b=1其实是一种情况,同时因为 a = = b a==b a==b的情况我们只算了一次,所以要加上一次 a = = b a==b a==b的情况,保证所有情况都出现了两次,再统一除上 2 。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int Max = 1e7 + 10;
int cas;
bool f[Max];
vector<int> prime;
void get_prime(){
    for(int i = 2; i < Max; i++) f[i] = true;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i)
            f[j] = false;
    }
    for(int i = 2; i < Max; i++)
        if(f[i]) prime.push_back(i);

}
ll divide(ll n){
    int len = prime.size();
    ll e ,ans = 1;
    //printf("len = %d\n",len);
    for(int i = 0 ; i < len; i++){
        e = 0;
        while(n % prime[i] == 0 && n > 1){
            e++;
            n /= prime[i];
        }
        ans *= (2 * e + 1) ;
        if(n == 1)
            break;
    }
    if(n > 1){
         ans *= (2 * 1 + 1) ;
    }
    return (ans + 1) / 2;

}
int main(){
    get_prime();
    scanf("%d",&cas);
    ll n;
    for(int c = 1; c <= cas; c++){
        scanf("%lld",&n);
        printf("Case %d: %lld\n",c,divide(n));
    }
    return 0;
}
I - Harmonic Number ——调和级数

在这里插入图片描述思路
所求的答案是一个调和级数。
直接使用上述公式

#include <bits/stdc++.h>
using namespace std;

const int Max = 1e4 + 10;
const double r = 0.57721566490153286060651209;
double a[Max];
int cas,n;
void solve(){
    a[0] = 0;
    for(int i = 1; i < Max; i++){
        a[i] = a[i-1] + 1.0/i;
    }
}
int main(){
    scanf("%d",&cas);
    solve();
    for(int c = 1; c <= cas; c++){
        scanf("%d",&n);
        if(n < Max)
            printf("Case %d: %.10f\n",c,a[n]);
        else
            printf("Case %d: %.10f\n",c,log(n) + r + 1.0/(2*n));
    }
    return 0;
}

J - Mysterious Bacteria——算术基本定理

题意
给一个 n n n,把 n n n表示为 b p b^p bp的形式,问最大的 p p p的值是多少。
思路
算术基本定理分解 ,然后找出一个最大值。
但是,这一题有个坑, n n n 可能是负数,这就要求 p p p n n n 为复数的时候要是一个奇数。
所以如果一开始分解出来的 p p p是一个偶数,要一直除以2,直到变成一个奇数为止才是答案。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int Max = 1e6 + 10;
bool f[Max];
vector<int> prime;
ll cas,n;

void get_prime(){
    for(int i = 2; i < Max; i++) f[i] = true;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = false;
        }
    }
    for(int i = 2; i < Max; i++)
        if(f[i]) prime.push_back(i);
}
ll divide(ll n){
    ll c,ans = -1;
    int len = prime.size();
    for(int i = 0; i < len; i++){
        c = 0;
        while(n % prime[i] == 0 && n > 1){
            n /= prime[i];
            c++;
        }
        if(n == 1)
            break;
    }
    if(n > 1) c = 1;
    return c;
}
int main(){
    get_prime();
    scanf("%lld",&cas);
    ll ans = 0;
    for(int c = 1; c <= cas; c++){
        scanf("%lld",&n);
        if(n < 0){
        	ans = divide(-n);
        	while(ans % 2 == 0){
        		ans /= 2;
			}
		}else{
			ans = divide(n);
		}
        printf("Case %d: %lld\n",c,ans);
    }
    return 0;
}

K - Large Division

题意
给两个大数 a , b a,b a,b,问 b   ∣   a b \ |\ a b  a是否成立。
思路
正常判断是否可以整除的时候,都是取余判断,大数的时候仍然可以模拟取余来进行大数的取余运算。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
#define ll long long
using namespace std;

const int Max = 250;
int num[Max];
int cas;
ll b;
string a;

int main(){
    scanf("%d",&cas);
    for(int c = 1; c <= cas; c++){
        ll s = 0,len;
        cin>>a;
        scanf("%lld",&b);
        if(b < 0) b = -b;
        len = a.length();
        if(a[0] == '-'){
            for(int i = 1; i < len; i++){
                s = (s * 10 + (a[i] - '0')) % b;
                //printf("s = %d\n",s);
            }
        }else{
            for(int i = 0; i < len; i++){
                s = (s * 10 + (a[i] - '0')) % b;
                    //printf("s = %d\n",s);
            }
        }
        //printf("s = %d\n",s);
        if(s == 0){
            printf("Case %d: divisible\n",c);
        }else
            printf("Case %d: not divisible\n",c);
    }
    return 0;
}

L - Fantasy of a Summation ——思维

题意
给出n,K,MOD,问下面这个程序中的res值为多少。

#include <stdio.h>

int cases, caseno;
int n, K, MOD;
int A[1001];

int main() {
    scanf("%d", &cases);
    while( cases-- ) {
        scanf("%d %d %d", &n, &K, &MOD);
        int i, i1, i2, i3, ... , iK;
        for( i = 0; i < n; i++ ) scanf("%d", &A[i]);
        int res = 0;
        for( i1 = 0; i1 < n; i1++ ) {
            for( i2 = 0; i2 < n; i2++ ) {
                for( i3 = 0; i3 < n; i3++ ) {
                    ...
                    for( iK = 0; iK < n; iK++ ) {
                        res = ( res + A[i1] + A[i2] + ... + A[iK] ) % MOD;
                    }
                    ...
                }
            }
        }
        printf("Case %d: %d\n", ++caseno, res);
    }
    return 0;
}

思路
我们可以明显的算出,res = ( res + A[i1] + A[i2] + ... + A[iK] ) % MOD;这条语句一共运行了 n k n^k nk次,可重全排列总共有 n k n^k nk种,一共有 n n n个数,所以每一个数在每一位上出现的次数为 n k / n = n k − 1 n^k/n = n^{k-1} nk/n=nk1,设 n n n个数之和为 s u m sum sum,则第一位上的总和 n k − 1 ∗ s u m n^{k-1}*sum nk1sum;又因为有 k k k重循环,即k位,每一位的权都是一样的,再乘上 k k k取余即可。
这里可以拿 n = 3 , k = 3 n = 3,k= 3 n=3k=3来举个例子。
一共有3^3 = 27种情况,因为一共只有3个数,所以在每一位上每个数出现的次数是27 / 3 = 9次。
在这里插入图片描述

#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll cas,n,k,m,val;

ll qpow(ll a,ll b,ll mod){
    ll res = 1;
    while(b){
        if(b & 1){
            res  = (res * a) % mod;
        }
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}
int main(){
    scanf("%lld",&cas);
    for(int c = 1; c <= cas; c++){
        ll sum = 0;
        scanf("%lld %lld %lld",&n,&k,&m);
        ll ans = qpow(n,k-1,m);
        for(int i = 0 ; i < n; i++){
            scanf("%lld",&val);
            sum = (sum + val) % m;
        }
        ans =  (k * sum) % m * ans % m;
        printf("Case %d: %lld\n",c,ans);
    }
    return 0;
}

M - Help Hanzo——素数区间筛法

题意
给出 a , b a,b a,b,求 a , b a,b a,b中素数的个数。
思路
由于本题的 a , b a,b a,b给的可能非常大,所以使用常规的筛法 + 前缀和是远远不够的。
这里用到了一个新知识点——区间筛法。

区间筛法
区间[a,b)指的是所有满足a<=x<b的整数(根据背景也可以是实数)所构成的集合。 b以内的合数的最小质因数一定不超过根号b,
如果有根号b以内的素数表的话,就可以把埃式筛法运用在[a,b)上了。 也就是说,先分别做好[2,根号b)的表和[a,b)的表,
然后从[a,根号b)的表中筛得素数得同时, 也将其倍数从[a,b)得表中划去,最后剩下的就是区间[a,b)内的素数。
——《挑战程序设计》

采用了一个技巧:将[a,b)的区间映射到了[0,b-a)上,这样就直接避免了可能存在的标记数组由于空间过大而开不了的情况。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int Max = 1e6 + 10;
bool f[Max],vis[Max];
ll a,b,cas,ans;
vector<ll> prime;
void get_prime(){
   for(int i = 2; i < Max; i++) f[i] = true;
   for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = false;
        }
   }
    for(int i = 2; i < Max; i++)
        if(f[i]) prime.push_back(i);
}
ll solve(){
    ll res = 0;
    for(ll i = 0; i <= b - a; i++) vis[i] = true;
    int len = prime.size();
    for(ll i = 0 ; i < len; i++){
        ll k = a / prime[i] * prime[i];
        if(k < a) k += prime[i];
        if(k < prime[i]*prime[i]) k =  prime[i]*prime[i];
        for(ll j = k; j <= b; j += prime[i])
            vis[j - a] = false;
    }
    for(ll i = 0; i <= b - a; i++)
        if(vis[i]) res++;
    if(a == 1)
        res--;
    return res;
}
int main()
{
    get_prime();
    scanf("%lld",&cas);
    for(int i = 1; i <= cas; i++)
    {
        scanf("%lld %lld",&a,&b);
        ans = solve();
        printf("Case %d: %lld\n",i,ans);
    }

    return 0;
}

N - Trailing Zeroes (III) ——二分答案

题意
给一个数 q q q ,找出一个 n n n,满足 n ! n! n! 的后缀 0 的个数为 q q q
思路
后缀0的个数问题是一个比较经典的问题,10可以分解为2*5,由于2的个数其实是远远大于5的,所以统计5的个数就是找到后缀0的关键,还要考虑一个问题就是什么时候后缀0的个数不存在,我们来看一下前几个5的倍数,5,10,15,20,25,30 对应的后缀0的个数是1,2,3,4,6,7大家可以发现是没有5的,因为25中有两个5,所以到25的时候,4+2 = 6,直接跳过了5。

使用二分答案进行查找,每次找出1~n中5的个数,就是 n ! n! n!的后置0的个数。
如果最后二分答案得到结果和询问的 q q q不同的话,那么就不存在答案。

下面是如何确定1~n中有几个5的过程。

    ll res = 0;
    while(n){
        res += n / 5;
        n /= 5;
    }

res就是答案, 以n == 100 来解释这个过程:

  1. n = 100时,在[1,100]内有floor(100/5)个数是可以整除5的
    ans += 100 / 5 ,ans = 20;
    n /= 5后,n = 20;
    这一步的实际含义其实是:[1,100]有多少数是5的倍数。
  2. n = 20时,在[1,20]内有floor(20/5)个数是可以整除5的
    ans += 20/5 ,ans = 24;
    这一步的实际含义其实是:[1,100]有多少数是25的倍数。

将[1,100]映射到了[1,20]上,我们可以发现在[1,20]上可以被5整除的数,就是在[1,100]上可以被25整除的数。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll Max = 9e8 + 10;
ll cas,q;
bool check(ll n){
    ll res = 0;
    while(n){
        res += n / 5;
        n /= 5;
    }
    //cout<<"res = "<<res<<"\n";
    if(res >= q)
        return true;
    else
        return false;

}
ll Erfen(ll l, ll r){

    while(l <= r){
        ll mid = (l + r) >> 1;
        //cout<<"l = "<<l<<" mid = "<<mid<<" r = "<<r<<"\n";
        if(check(mid)) r = mid - 1;
        else l = mid + 1;
    }
    return l;
}
int main(){
    scanf("%lld",&cas);
    for(int c = 1; c <= cas; c++){
        scanf("%lld",&q);
        ll ans = Erfen(1,Max);
        ll cnt = 0,t = ans;
        while(t){
            cnt += t / 5;
            t /= 5;
        }
        if(q != cnt){
            printf("Case %d: impossible\n",c);
        }else{
            printf("Case %d: %lld\n",c,ans);
        }
    }
    return 0;
}

O - GCD - Extreme (II)

题意

N N N,求出 ∑ i = 1 i < N ∑ j = i + 1 j < = N G C D ( i , j ) \displaystyle\sum_{i=1}^{i<N}\sum_{j=i+1}^{j<=N}GCD(i,j) i=1i<Nj=i+1j<=NGCD(i,j)

就是给出一个数 N N N,找到1~ N N N中每两个数的gcd值,求这些gcd的和。

思路
找出所有互质的数,这些数的gcd就是1,找到1~n与n互质的个数就很容易想到欧拉函数,所以所有数的欧拉函数和就是所有互质的数的gcd和。

那就剩下不互质的数了。
可以在求欧拉函数的时候找到那些不互质的数的gcd。

假设 a , b a,b a,b是互质的,即 g c d ( a , b ) = 1 gcd(a,b) = 1 gcd(a,b)=1。那么 g c d ( 2 a , 2 b ) = 2 gcd(2a,2b) = 2 gcd(2a,2b)=2,…, g c d ( k a , k b ) = k gcd(ka,kb) = k gcd(ka,kb)=k,这样
就可以通过枚举 k 来求出不互质的数的的gcd。
欧拉函数求出所有的n前面与n互质的个数,这样所有的与n互质的个数都可以像上面那样扩展,这样就可以枚举到所有不互质的数。

用一个数组ans来存下每个n和n前面的数的gcd值的和,最后再求一个前缀和就可以得到答案。
可能说的不是很清楚,来举个例子:
N = 9 N = 9 N=9,开始枚举1到9的所有元素

枚举1,1前面没有数,ans[1] = 0;

枚举2,2的欧拉函数为1,即2前面有一个数1和2互质,
开始枚举k
k = 1,ans[1*2] += φ(2)*1 = 1; 表示1和2的gcd = 1
k = 2,ans[2*2] += φ(2)*2 = 2; 表示2和4的gcd = 2
k = 3,ans[3*2] += φ(2)*3 = 3; 表示3和6的gcd = 3
k = 4,ans[4*2] += φ(2)*4 = 4; 表示4和8的gcd = 4
k = 5,2 * 5 > 9,退出 。

枚举3,3的欧拉函数为2,3的前面有两个数1,2和3互质。
k = 1,ans[1*3] += φ(3)*1 = 2; 表示 1 和 3的gcd = 1加上 2 和 3 的gcd = 1,一共为2
k = 2,ans[2*3] += φ(3)*2 = 4; 表示 2 和 6的gcd = 2加上 4 和 6 的gcd = 2,一共为4
k = 3,ans[3*3] += φ(3)*3 = 6; 表示 3 和 9的gcd = 3加上 6 和 9 的gcd = 3,一共为6
k = 4,4 * 3 > 9,退出 。

枚举4,4的欧拉函数为2,4的前面有两个数1,2和4互质。
k = 1,ans[1*4] += φ(4)*1 = 2;
k = 2,ans[2*4] += φ(4)*2 = 4;
k = 3,3 * 4 > 9,退出 。

枚举5,5的欧拉函数为4,5的前面有四个数1,2,3,4和5互质。
k = 1,ans[1*5] += φ(5)*1 = 4;
k = 2,2 * 5 > 9,退出 。

枚举6,6的欧拉函数为2,6的前面有两个数1,6和6互质。
k = 1,ans[1*6] += φ(6)*1 = 2;
k = 2,2 * 6 > 9,退出 。

枚举7,7的欧拉函数为6,7的前面有六个数1,2,3,4,5,6和7互质。
k = 1,ans[1*7] += φ(7)*1 = 6;
k = 2,2 * 7 > 9,退出 。

枚举8,8的欧拉函数为4,8的前面有四个数1,3,5,7和8互质。
k = 1,ans[1*8] += φ(8)*1 = 4;
k = 2,2 * 8 > 9,退出 。

枚举9,9的欧拉函数为6,7的前面有六个数1,2,4,5,7,8和9互质。
k = 1,ans[1*9] += φ(9)*1 = 6;
k = 2,2 * 9 > 9,退出 。

累加前的ans[1~9]为
0 1 2 4 4 9 6 12 12
之后求前缀和即可。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll Max = 4e6 + 10;
ll n;
ll phi[Max], ans[Max];

ll gcd(ll a, ll b) {
    return b = 0 ? 1 : gcd(b, a % b);
}
void get_euler() {
    for(ll i = 1; i < Max; i++) phi[i] = i;
    for(ll i = 2; i < Max; i++) {
        if(phi[i] == i) {
            for(ll j = i; j < Max; j += i) {
                phi[j] = phi[j] / i * (i - 1);
            }
        }
        for(ll j = 1; j * i < Max; j++) {
            ans[j * i] += phi[i] * j;
        }
    }
    for(int i = 1; i <= 9; i++)
        cout<<ans[i]<<" ";
    for(int i = 1; i < Max; i++)
        ans[i] += ans[i - 1];

}
int main() {
    get_euler();
    while(scanf("%lld", &n) && n) {
        printf("%lld\n", ans[n]);
    }
    return 0;
}
R - 青蛙的约会——扩展欧几里得求线性同余方程

此题是扩展欧几里得的经典题目
题意:
两只青蛙A和B在一条首尾相接的线段上,线段长为L,两蛙要见面,方向相对,A的起始位置是X,每次可以跳m米,B的起始位置是Y,每次可以跳n米,问什么它们跳几次可以见面。
思路:
求什么我们就设什么,设两只蛙,跳了T步相遇,此时A的坐标是X + mT,B的坐标就是Y + nT,当A和B的坐标的差值是L的倍数的时候,两只蛙可以相遇。
那么我们可以得到一个方程:
X + m T − ( Y + n T ) = L P ( P   ∈ Z ) X+mT - (Y+nT) = LP(P\ \in Z) X+mT(Y+nT)=LP(P Z) ( n − m ) T + L P = X − Y , L > 0 (n-m)T + LP = X-Y,L>0 nmT+LP=XYL>0

这样就可以使用扩展欧几里得算法来解题了,T和P就是待求的未知元。

  • 求出 ( n − m ) {(n- m)} (nm) l {l} l 的最大公约数 g g g,如果 g g g不能整除 X − Y {X-Y} XY,则无解。

  • 如果可以整除,设 a ′ = ( n − m ) / g , b ′ = L / g , c ′ = ( X − Y ) / g {a^{'} = (n-m)/g,b^{'} = L/g ,c^{'} = (X-Y)/g} a=(nm)/g,b=L/g,c=(XY)/g。 先用Exgcd( a ′ , b ′ , x 0 , y 0 a^{'},b^{'},x_0,y_0 a,b,x0,y0)求出 a ′ x ′ + b ′ y ′ = 1 {a^{'}x^{'} + b^{'}y^{'} = 1} ax+by=1的一组特解,也就是 a ′ c ′ x ′ + b ′ c ′ y ′ = c ′ {a^{'} c^{'}x^{'} + b^{'}c^{'}y^{'}= c^{'}} acx+bcy=c的一组特解, ( x 0 , y 0 ) {(x_0,y_0)} (x0,y0)

  • 接着就可以求出原方程的一组特解 t 0 = c ′ x ′ {t_0 = c^{'}x^{'}} t0=cx

  • 通过求出来的特解 t 0 {t_0} t0,求出通解: t = t 0 + b ′ k = x ′ × ( X − Y ) / g   +   L / g ∗ k 。 ( k ∈ Z ) {t = t_0 +b^{'}k = x^{'}\times(X-Y)/g\ +\ L/g*k}。(k \in Z) t=t0+bk=x×(XY)/g + L/gkkZ

  • 找出最小的正整数解 a n s = ( t   %   b ′ + b ′ )   %   b ′ {ans = (t \ \%\ b^{'}+b^{'})\ \%\ b^{'} } ans=(t % b+b) % b

#include <bits/stdc++.h>
#define ll long long
using namespace std;

ll exgcd(ll a,ll b,ll &x,ll &y) {
	if(b == 0) {
		x = 1;
		y = 0;
		return a;
	}
	ll t = exgcd(b,a%b,x,y);
	ll x1 = x,y1 = y;
	x = y1;
	y = x1 - a / b * y1;
	return t;
}
int main() {
	ll a0,b0,c0,g,m,n,X,Y,L,t0,x0,y0,ans;
	scanf("%lld%lld%lld%lld%lld",&X,&Y,&m,&n,&L);
	g = exgcd(n-m,L,x0,y0);
	if((X-Y)%g != 0 || m == n)
		printf("Impossible\n");
	else {
		a0 = (n-m)/g;
		b0 = L/g;
		c0 = (X-Y)/g;
		exgcd(a0,b0,x0,y0);
		t0 = c0*x0;
		//printf("g = %d\nb0 = %d \nt0 % b0 = %lld \n",g,b0,t0 % b0);
		ans = (t0 % b0 + b0)%b0;
		printf("%lld\n",ans);
	}
	return 0;
}
S - C Looooops ——扩展欧几里得求线性同余方程

题意
在一个 2 k 2^k 2k的处理器上运行下面的程序:

for (variable = A; variable != B; variable += C)
  statement;

给出A,B,C,k,问statement;执行了多少次。
思路
位数就是我们取模的模数,求解一阶线性同余方程。
设一共运行了X次。
X * C ≡ \equiv (B - A)( mod 2 k 2^k 2k)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;

ll exgcd(ll a, ll b, ll &x, ll &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    ll t = exgcd(b, a % b,x,y);
    ll x1 = x,y1 = y;
    x = y1;
    y = x1 - a / b * y1;
    return t;
}
int main(){
    ll a,b,c,k,x,y,ta,tb,tc,x0;
    while(scanf("%lld %lld %lld %lld",&a,&b,&c,&k) && a + b + c + k != 0){
        ta = c;
        tb = 1ll << k;
        ll g = exgcd(ta,tb,x,y);
        tc = b - a;
        if(tc % g != 0) printf("FOREVER\n");
        else{
            x0 = tc / g * x % tb;
            ll ans = (x0 % (tb / g) + tb / g ) % (tb / g);
            printf("%lld\n",ans);
        }
    }
    return 0;
}
U - Primes ——素数筛

题意
判断一个是不是素数,同时规定1和2不是素数。
思路
打表判断素数。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const ll Max = 2e4;
bool f[Max];

void get_prime(){
    for(ll i = 2ll; i < Max; i++) f[i] = true;
    for(ll i = 2ll; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = false;
        }
    }
    f[1] = false;
    f[2] = false;
}
int main(){
    int cnt = 1,n;
    get_prime();
    while(scanf("%d",&n) != EOF && n > 0){
        if(f[n])
            printf("%d: yes\n",cnt++);
        else
            printf("%d: no\n",cnt++);
    }
    return 0;
}
V - Maximum GCD

题意
给m个数,找出这m个数中两两配对最大的gcd值。
思路
用字符串读取后分割出数字,然后直接暴力求出每两个的gcd值,比较即可。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <sstream>
#define ll long long
using namespace std;

vector<ll> a;
ll gcd(ll a,ll b){
    return b == 0ll ? a : gcd(b, a % b);
}
int main(){
    ll n;
    scanf("%lld",&n);
    getchar();
    string str;
    while(n--){
        getline(cin,str);
        ll maxx = -1;
        stringstream ss(str);
        a.clear();
        int tmp;
        while(ss >> tmp){
            a.push_back(tmp);
        }
        ll size_ = a.size();
        for(ll i = 0ll ; i < size_ - 1ll; i++){
            for(ll j = i + 1ll; j < size_; j++){
                maxx = max(maxx,gcd(a[i],a[j]));
            }
        }
        printf("%lld\n",maxx);
    }
    return 0;
}
W - Prime Time

题意
给一个区间[L,R],区间中有数 n n n满足, n 2 + n + 41 n^2+n+41 n2+n+41是一个质数。问区间中满足条件的数,占区间的百分之几?
思路
枚举每个 i 如果 i 满足条件,则直接将dp[i]设为1,然后进行前缀和相加,求出区间内的个数的时候使用前缀的性质dp[R] - dp[L-1]即可。

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#define ll long long
using namespace std;

const int Max = 1e4 + 10;
int dp[Max];

ll formula() {
    for(ll i = 0 ; i < Max; i++) {
        ll num = i * i + i + 41;
        ll flag = 0;
        for(ll j = 2; j * j <= num; j++) {
            if(num % j == 0) {
                flag = 1;
                break;
            }
        }
        if(!flag)//素数
            dp[i] = 1;
    }
    for(ll i = 1; i <= Max; i++)
        dp[i] += dp[i - 1];
}
int main() {
    double ans,fenzi,fenmu;
    int a,b;
    formula();
    while(scanf("%d %d", &a, &b) != EOF) {
        fenmu = b - a + 1;
        if(a == 0) {
            a = 1;
            fenzi = (dp[b] - dp[a - 1] + 1);
        }else fenzi = (dp[b] - dp[a - 1] );
        ans = fenzi / fenmu;
        printf("%.2f\n", ans * 100.0 + 1e-8);
    }
    return 0;
}
X - Farey Sequence

题意
每个数m可以和m前面并且和m互质的m组成分数。例如:
F2 = {1/2}
F3 = {1/3, 1/2, 2/3}
F4 = {1/4, 1/3, 1/2, 2/3, 3/4}
F5 = {1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5}
给一个数n,求出Fn中的元素个数。
思路
欧拉函数的应用,打表求出欧拉函数 然后使用前缀和。

#include <cstdio>
#include <algorithm>
#include <vector>
#define ll long long
using namespace std;

const int Max = 1e6 + 10;
ll phi[Max];
bool f[Max];
vector<ll> prime;

void get_prime(){
    for(ll i = 2; i < Max; i++) f[i] = true;
    for(ll i = 2; i < Max; i++){
        for(ll j = i * i; j < Max; j += i){
            f[j] = false;
        }
    }

    for(ll i = 2; i < Max; i++){
        if(f[i]) prime.push_back(i);
    }
}
void get_phi(){
    for(ll i = 1; i < Max; i++) phi[i] = i;
    for(ll i = 2; i < Max; i++){
        if(phi[i] == i){
            for(ll j = i ; j < Max; j += i){
                phi[j] = phi[j] / i * (i - 1);
            }
        }
    }
    phi[1] = 0;
    for(ll i = 2; i < Max; i++)
        phi[i] += phi[i - 1];
}
int main(){
    ll n;
    get_phi();
    while(scanf("%lld",&n) != EOF && n){
        printf("%lld\n",phi[n]);
    }
    return 0;
}
Y - The Super Powers

题意
一个数可以分解成至少两个数的幂形式,就把这个数叫超级幂。例如:
64 = 8 2   a n d   64 = 4 3 , 64 64 = 8^2 \ and \ 64 = 4^3,64 64=82 and 64=43,64就是一个超级幂。
思路
底数在[2,1<<16]内,指数是64以内的合数。底数为a的幂不会溢出的上限为ceil(64/((log(a)/log(2)))-1。
指数是合数 就可以保证可以分解成多个数的幂形式。
例如:
64 = 2 6 , 6 = 2 ∗ 3 64 = 2^6,6 = 2*3 64=266=23
64 = ( 2 2 ) 3 = 4 3 = ( 2 3 ) 2 = 8 2 64 = (2^2)^3=4^3 = (2^3)^2 =8^2 64=(22)3=43=(23)2=82.

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>
#include <vector>
#include <cmath>
#define ll unsigned long long
using namespace std;

bool f[70];
vector<ll> Compos;

int len;

void init(){
   for(ll i = 2; i <= 64; i++) f[i] = 0;
   for(ll i = 2; i <= 64; i++){
       for(ll j = i * i;  j <= 64; j += i)
           f[j] = 1;
   }
   for(int i = 4; i <= 64; i++)
       if(f[i]) Compos.push_back(i);
   len = Compos.size();
}
void solve(){
   set<ll> ans;
   ll limit1 = 1 << 16;
   ll t;
   ans.insert(1);

   for(ll i = 2; i < limit1; i++){
       ll limit2 = ceil(64 * log(2) / log(i)) - 1;
       ll tmp = i * i * i * i;
       ans.insert(tmp);
       for(ll j = 1; Compos[j] <= limit2 ; j++){
           tmp *= Compos[j] - Compos[j - 1] == 1 ? i : i * i;
           ans.insert(tmp);
       }
   }
   for(set<ll>::iterator it = ans.begin(); it != ans.end(); it++){
       printf("%llu\n",*it);
   }
}
int main(){
   init();
   solve();
   return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值