简单数论 | Day3 部分题解

  A - The Euler function 来源:HDU 2824

    计算[a,b]区间内的整数的欧拉函数值,需要掌握单个欧拉函数和函数表的使用。

#include <iostream>
#include <cstdio>
using namespace std;

const int MAX_N  = 3000010;
typedef long long ll;
int phi[MAX_N];
// ll sum_phi[MAX_N];  若使用前缀和累加,会爆内存(MLE)

void phi_table(int n)
{  // 计算得到n以内的欧拉函数表,参考蓝书P121
    int i, j;
    for(i=1;i<=n;i++)
        phi[i] = i;
         
    for(i=2;i<=n;i+=2)
        phi[i] /= 2;
         
    for(i=3;i<=n;i+=2)
    {
        if(phi[i]==i)
        {
            for(j=i;j<=n;j+=i)
                phi[j] = phi[j] / i * (i - 1);
        }
    }
}

int main()
{
    phi_table(3000000);

// for(int i=1;i<=3000000;i++)
// sum_phi[i] = sum_phi[i-1] + phi[i];

// int a, b;
// while(cin>>a>>b)
// printf("%lld ", sum_phi[b]-sum_phi[a-1]);

int a, b;
    while(cin>>a>>b)
    {
        ll sum = 0;
        for(int i=a;i<=b;i++)
            sum += phi[i];
        printf("%lld
", sum);
    }
    return 0;
}

    B - Divisors  来源:POJ 2992 

    求组合数C(n, k)的因子个数,0 ≤ k ≤ n ≤ 431。

    打表可得431以内的素数只有83个,由于C(n, k) = n!/(k!*(n-k)!) = n*(n-1)*…*(n-k+1)/k!,开始的直观想法是求出分子上的每个素数因子的总个数,再减去分母上出现的每个素数因子的总个数,正约数的个数即为(a1+1)*(a2+1)*…*(an+1)。然而多次提交优化仍然超时。。。最后参考https://www.cnblogs.com/zxhyxiao/p/8026280.html,学到了计算N!中某个素因子个数的计算方法,结合网上的做法,终于AC。

求N!中素因子p的个数,也就是N!中p的幂次

公式为:cnt=[n/p]+[n/p^2]+[n/p^3]+...+[n/p^k]

核心代码:

int cnt = 0;
while(N)
{   cnt += N/p;   N /= p; }
 
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int p[200], cnt, num[440];
bool notprime[440];

void prime_table(int n)
{ // 筛素数
  notprime[0] = notprime[1] = 1;
  int i, j;
  for(i=2;i<=n;i++)
  {
    if(!notprime[i])  
           {
                num[i] = cnt;
                p[cnt++] = i;
           }  

    for(j=0;j<cnt && i*p[j]<n;j++)
    {
      notprime[i*p[j]] = 1;
      if(i%p[j]==0) break;
    }
  }
}


int main()
{
    
    prime_table(431);
//    for(int i=0;i<cnt;i++) cout<<p[i]<<endl;
//    cout<<cnt<<endl;
    int n, k;
    while(scanf("%d %d", &n, &k)!=EOF)
    {
        long long ans = 1;
        for(int i=0;i<cnt && p[i]<=n;i++)
        {
            int N = n, cnt = 0;
            while(N)
            {
                cnt += N/p[i];
                N /= p[i];
            }
            N = k;
            while(N)
            {
                cnt -= N/p[i];
                N /= p[i];
            }
            N = n-k;
            while(N)
            {
                cnt -= N/p[i];
                N /= p[i];
            }
            ans *= (cnt+1);
        }
        printf("%lld
", ans);
    } 
    return 0;
}

    C - Longge's problem 来源:POJ 2480

    计算∑gcd(i, N), 1<=i<=N

    简单推导可得所求结果为∑i*phi(n/i), i|n.由于n的上限太大(1e18),书上打表的办法失效,一时陷入困境。再次百度参考https://www.cnblogs.com/flipped/p/5690123.html才茅塞顿开,直接采用O(√n)求单个欧拉函数值。需要特别注意注释处,最开始好几次TLE都是因为i*i结果为int类型溢出变为负数,出现了死循环。

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

typedef long long ll;

ll euler(int x)
{  // 计算欧拉函数phi(x)
    int res=x, m = (int)sqrt(x+0.5);
    for(int i=2; i<=m; i++)
        if(x%i==0)
        {
            res = res/i*(i-1);
            while(x%i==0) x/=i;
        }
    if(x>1) res = res/x*(x-1);
    return res;
}

int main()
{
    ll n, ans;
    while(scanf("%lld", &n)!=EOF)
    {
        ans = 0;
        int i;
        for(i=1;i<n/i;i++)  // for(i=1;(ll)i*i<n;i++) 
        {
            if(n%i==0)  ans += i*euler(n/i) + n/i*euler(i);
        }
        if(i*i==n)  ans += i*euler(i);
        printf("%lld
", ans);
    }
}

    E - Happy 2006 来源:POJ 2773 

    计算与m互质的第K大的正整数,m (1 <= m <= 1000000), K (1 <= K <= 100000000)

    直接上AC代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

int gcd(int a,int b)
{
    if(!b) return a;
    return gcd(b,a%b);
}

int euler(int x)
{
    int res=x, m = (int)sqrt(x+0.5);
    for(int i=2; i<=m; i++)
        if(x%i==0)
        {
            res = res/i*(i-1);
            while(x%i==0) x/=i;
        }
    if(x>1) res = res/x*(x-1);
    return res;
}

int main()
{
    int m, k;
    while(scanf("%d %d", &m, &k)!=EOF)
    {
        int sum = euler(m);
        int t = k % sum, tt = 0, i;
        if(t==0) t = sum;   //注意整除的处理
        for(i=1;i<m;i++)
        {
            if(gcd(i, m)==1) tt++;
            if(tt==t)  break;
        }

        printf("%d
", i+(k-1)/sum*m);
    }
}

  貌似以上做法的欧拉函数显得多余,直接用gcd判断m以内全部互质的整数,总个数即为sum。

    D - GCD & LCM Inverse  来源:POJ 2429 

    最后AC的D题,参考了网上用dfs得到数组中最接近sqrt(N)的部分元素之积。晚上讲题解时滕佬指明一定要用Miller Rabin算法,直接在其模板基础上修改而来。代码十分长而且显得有些杂乱,日后再做整理吧~~

#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
#include <cmath>
#include <algorithm>
using namespace std;

typedef long long ll;

ll box[1000];
ll N, T, num;

ll min(ll a , ll b)
{
    return (a < b) ? a : b;
}

ll gcd(ll a , ll b)
{
    while(b)
    {
        ll t = a;
        a = b;
        b = t % b;
    }
        
    return a;
}

ll tim(ll a , ll b , ll mo)
{
    a %= mo;
    b %= mo;
    
    ll ans = 0;
    
    while(b)
    {
        if(b & 1)
        {
            ans += a;
            if(ans > mo)
                ans -= mo;
        }
            
        b >>= 1;
        a <<= 1;
        
        if(a > mo)
            a -= mo;
    }
    
    return ans;
} 

ll po(ll x , ll n , ll mo)
{
    x %= mo;
    ll ans = 1;
    
    while(n)
    {
        if(n & 1)
            ans = tim(ans , x , mo);
        
        x = tim(x , x , mo);
        n >>= 1;
    }
    
    return ans;
}

bool tes(ll n , ll d , ll t , ll mo)
{
    
    ll y = po(t , n , mo);
        
    ll i;
    for(i = 0 ; i < d ; i++)
    {
        ll no = tim(y , y , mo);
        
        if(y != mo - 1 && y != 1 && no == 1)
        {
            return false;
        }
            
        y = no;
    }
    
    if(y != 1)
    {
        return false;
    }
    
    return true;
}

bool MR(ll n)
{
    if(n < 2)
        return false;
    
    if(n == 2 || n == 3)
        return true;
        
    if((n & 1) == 0 || n % 3 == 0)
        return false;
        
    ll b = n - 1, t = 0;
    
    while((b & 1) == 0)
    {
        b >>= 1;
        t++;
    }
    
    ll i, ty[8] = {2 , 3 , 7 , 61 , 24251 , 657 , 6436 , 123};
    
    for(i = 0 ; i < 8 ; i++)
    {
        if(!tes(b , t , ty[i] , n) && ty[i] < n)
        {
            return false;
        }
    }
    
    return true;
}

ll Ro(ll n , ll c)
{
    srand(time(NULL));
    ll x0 = rand() % (n - 1) + 1;
    
    ll x = x0, y = x0, k = 0, i = 1, d = 1;
    
    while(d == 1)
    {
        k++;
        x = (tim(x , x , n) + c) % n;
        
        d = gcd(-1 * min(x - y , y - x) , n);
        
        if(d > 1 && d < n)
            return d;
        
        if(d == n)
            return -1;
            
        if(k == i)
        {
            y = x;
            i <<= 1;
        }
    }
}

void fj(ll n)
{
    if(n == 1)
        return;
    
    if(MR(n))
    {
        box[num++] = n;
        return;
    }
    
    ll p = -1, c = 107;
    
    while(p == -1)
        p = Ro(n , c--);

    fj(n / p);
    fj(p);
}

ll fact[100], common;
int cnt = 0;
void dfs(ll now, int n)
{
    if(now>sqrt(N))  return;
    common = max(common, now);
    
    for(int i=n;i<=cnt;i++)  dfs(now*fact[i], i+1);
}

int main()
{
    
    ll lcm, gcd;
    while(scanf("%lld %lld", &gcd, &lcm)!=EOF)
    { 
        N = lcm / gcd;

        MR(N);
        num = 0;
        fj(N);
        
        sort(box, box+num);
//        for(int i=0;i<num;i++)
//            printf("%lld
", box[i]);
            
        cnt = 0;
        for(int i=1;i<100;i++)  fact[i] = 1;
        fact[0] = box[0];
        for(int i=1;i<num;i++)
        {
            if(box[i]==box[i-1])  fact[cnt] *= box[i];
            else  fact[++cnt] = box[i];
        }
        //for(int i=0;i<=cnt;i++)  printf("%lld
", fact[i]);
        
        common = 1; 
        dfs(1, 0);
        printf("%lld %lld
", common*gcd, lcm/common*gcd);
    }
    
    return 0;
}
View Code

    

   END.

原文地址:https://www.cnblogs.com/izcat/p/9417020.html