zoukankan      html  css  js  c++  java
  • 数论专场 Day9 部分题解

     // 2019年西电暑期集训

     // [7月9日]基础数论:https://cn.vjudge.net/contest/309097

    A - Visible Lattice Points

     题目大意:

    平面上有N*N个格点,问从原点(0,0)能够看到多少个不被遮挡的点。

    数据范围:1<=N<=1000,测试组数:1<=C<=1000。

     分析及代码:

    暴力计算复杂度N*N*C,肯定T。看题目所给几组样例,也没有发现有什么规律。

    注意到点的分布的对称性,对于每一列(x=xi),记录在直线y=x以下能看到的点的坐标:

    (1,1)

    (2,1)

    (3,1),(3,2)

    (4,1),(4,3)

    (5,1),(5,2),(5,3),(5,4)

    ......

    可以发现直线x=xi上的点的个数为与xi互质的个数,即欧拉函数值phi(xi)。

    由对称性,(1,1)重复计算,加上点(1,0)与(0,1),所以本题答案为2*∑phi(xi) + 1。

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int maxn  = 1010;
    int phi[maxn+1];
    
    void phi_table(int n) { // O(nlogn) n以内欧拉表
        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);
            }
        }
    
        for(int i=1;i<=n;i++) {  // 前缀和
            phi[i] += phi[i-1];
        }
    }
    
    int main()
    {
        phi_table(1010);
        int T, t = 0, n;
        cin>>T;
        while(t++<T) {
            scanf("%d", &n);
            printf("%d %d %d
    ", t, n, phi[n]*2+1);
        }
        return 0;
    }
    View Code

    B - Super A^B mod C

     题目大意:

     计算A^B (mod C)的结果,其中1<=A,C<=1000000000,1<=B<=10^1000000。

     分析及代码:

    B的数位长度达到了1000000位,即使利用快速幂logn的算法也有些勉强。。。

    依稀记得有一个欧拉定理(费马小定理),计算当m为素数时a^b % m结果为a^(b%(m-1)),指数立刻收缩到了ll范围内。

    这里需要用到a和m不互质情况下的欧拉降幂公式:

    至于为什么我AC的代码指数没有加上phi(C)也过了,我也不懂O.O

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    const int maxn = 1000100;
    typedef long long ll;
    ll a, c;
    char b[maxn];
    
    ll phi(ll x) {  // 欧拉函数
        ll res = x;
        for(int i=2;i<=x/i;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;
    }
    
    ll pow(ll a, ll n) { // 快速幂
        ll res = 1;
        while(n) {
            if(n&1) res = res * a % c;
            a = a*a % c;
            n >>= 1;
        }
        return res;
    }
    
    int main() {
        while(scanf("%lld", &a)!=EOF) {
            scanf("%s", b);
            scanf("%d", &c);
            ll exp = 0, p = phi(c);
            for(int i=0;b[i];i++) {
                exp *= 10;
                exp += b[i]-'0';
                exp %= p;
            }
            // exp += p;
            printf("%lld
    ", pow(a, exp));
        }   
        return 0;
    }
    View Code

     

    D - The Euler function

     题目大意:

    求一段区间的欧拉函数值的和。(2<a<b<3000000)

     分析及代码:

    欧拉函数裸题,使用O(nlogn)欧拉筛预处理。

    (预先求前缀和的算法比直接区间求和要慢。。。)

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int maxn  = 3000010;
    typedef long long ll;
    ll phi[maxn];
    
    void phi_table(int n)
    { 
        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);
            }
        }
    
        for(int i=1;i<=n;i++) {
            phi[i] += phi[i-1];
        }
    }
    
    int main()
    {
        phi_table(3000000);
        int a, b;
        while(scanf("%d %d", &a, &b)!=EOF) { // 此题直接写for循环更快
            printf("%lld
    ", phi[b]-phi[a-1]);
        }
        return 0;
    }
    View Code

    E - Strange Way to Express Integers

     题目大意:

    中国剩余定理裸题。。。

     分析及代码:

    还不是特别熟悉,网上找了份mi不互质的模板,稍微改改就直接A了。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 1010;
    ll exgcd(ll a, ll b, ll& x, ll& y) {
        ll d = a;
        if(b!=0) {
            d = exgcd(b, a%b, y, x);
            y -= (a/b) * x;
        } else {
            x = 1; y = 0;
        }
        return d;
    }
    
    ll a[maxn], m[maxn], n;
    ll CRT() {
        if (n == 1) {
            if (m[0] > a[0]) return a[0];
            else return -1;
        }
        ll x, y, d;
        for (int i = 1; i < n; i++) {
            if (m[i] <= a[i]) return -1;
            d = exgcd(m[0], m[i], x, y);
            if ((a[i] - a[0]) % d != 0) return -1;   //不能整除则无解
            ll t = m[i] / d;
            x = ((a[i] - a[0]) / d * x % t + t) % t; //第0个与第i个模线性方程的特解
            a[0] = x * m[0] + a[0];
            m[0] = m[0] * m[i] / d;
            a[0] = (a[0] % m[0] + m[0]) % m[0];
        }
        return a[0];
    }
    
    int main() {
        while(scanf("%lld", &n)!=EOF) {
            for(int i=0;i<n;i++) {
                scanf("%lld %lld", &m[i], &a[i]);
            }
            printf("%lld
    ", CRT());
        }   
        return 0;
    }
    View Code

    F - C Looooops

     题目大意:

    求A + Cx = B (mod 2^k) 的x最小正整数解。

     分析及代码:

    上式变形得到

    C*x + 2^k*y = B-A

    利用扩展欧几里得ax+by=gcd(a,b)解该同余方程,得到x。

    • 若 (B-A) % gcd(a, b) 不为0,则无解。
    • 否则 x = x * (B-A) / gcd(a, b) ,得到 原方程的特解。
    • 原方程的全部解为 x + m * C / gcd(a, b), m∈Z
      • 令 s = C / gcd(a, b)
      • 所以最小的整数解为   (x%s + s) % s
    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    // d = gcd(a, b) = ax + by
    ll exgcd(ll a, ll b, ll& x, ll& y) {
        ll d = a;
        if(b!=0) {
            d = exgcd(b, a%b, y, x);
            y -= (a/b) * x;
        } else {
            x = 1; y = 0;
        }
        return d;
    }
    // Ax = B (mod C)
    ll solve(ll A, ll B, ll C){
        ll x, y;
        ll d = exgcd(A, C, x, y);
        if(B%d!=0){
            return -1;  // 无解
        }
        x *= B/d;
        ll s = C/d;
        return (x%s + s)%s;
    }
    int main()
    {
        int A, B, C, k;
        while(scanf("%d %d %d %d", &A, &B, &C, &k)!=EOF && A+B+C+k) {
            ll ans = solve(C, B-A, (ll)1<<k);   // A+Cx = B (mod 2^k)
            if(ans==-1) {
                printf("FOREVER
    ");
            } else {
                printf("%lld
    ", ans);
            }
        }
        return 0;
    }
    View Code

    G - Divisors

     题目大意:

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

     分析及代码:

     一看n,k都不大,直接将定义式的连乘项分解,统计每个素因子的出现次数。

    然后有那啥公式,每个素因子个数+1后再相乘即为答案,交一发T了。

    加上素数筛,还是T了。优化 了下,T了。改对n,k的记忆化,还是T了。

    。。。

    来学习一下求n!某个因子个数的方法:(去年博客

     // 公式:cnt = [n/p] + [n/p^2] + [n/p^3] +...+ [n/p^k]
    
     // 核心代码:
    
    int cnt = 0;
    
    while(n) {
      cnt += n/p;
    
      n /= p;
    
    }

    终于AC的代码:

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn = 440;
    int cnt[maxn], n, k;
    int p[maxn], num;
    void init() {
        for(int i=2;i<maxn;i++) {
            bool isp = true;
            for(int j=2;j*j<=i;j++) {
                if(i%j==0) {
                    isp = false;
                    break;
                }
            }
            if(isp) p[num++] = i;
        }
    }
    
    int cal(int n, int p) {
        int res = 0;
        while(n) {
            res += n/p;
            n /= p;
        }
        return res;
    }
    int main()
    {
        init();
        while(scanf("%d %d", &n, &k)!=EOF) {
            long long ans = 1;
            for(int i=0;i<num && p[i]<=n;i++) {  // 没有p[i]<=n TLE!!!
                long long cnt = 0;
                cnt += cal(n, p[i]);
                cnt -= cal(k, p[i]);
                cnt -= cal(n-k, p[i]);
                ans *= (cnt+1);
            }
            printf("%lld
    ", ans);
        }
        return 0;
    }
    View Code
    
    
    
    

    H - 青蛙的约会

     题目大意:

    同F题,求同余方程 x-y + (m-n) * k = 0 (mod L) k的最小正整数解。

     分析及代码:

     按照F的推导过程慢慢推吧。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    // d = gcd(a, b) = ax + by
    ll exgcd(ll a, ll b, ll& x, ll& y) {
        ll d = a;
        if(b!=0) {
            d = exgcd(b, a%b, y, x);
            y -= (a/b) * x;
        } else {
            x = 1; y = 0;
        }
        return d;
    }
    // Ax = B (mod C)
    ll solve(ll A, ll B, ll C){
        ll x, y;
        ll d = exgcd(A, C, x, y);
        if(B%d!=0){
            return -1;  // 无解
        }
        x *= B/d;
        ll s = C/d;
        return (x%s + s)%s;
    }
    
    int main()
    {
        ll x, y, m, n, L;
        cin>>x>>y>>m>>n>>L;
        ll ans;
        if(m>n) ans = solve(m-n, y-x, L);   // m-n>0
        else ans = solve(n-m, x-y, L);
        if(ans==-1) {
            printf("Impossible
    ");
        } else {
            printf("%lld
    ", ans);
        }
        return 0;
    }
    View Code

    I - Semi-prime H-numbers

     题目大意:

    定义类似4*k+1的正整数为H数。H数分为两种,H素数和H合数,合数中只能分解成两个H素数相乘形式的为H半素数。

    求一个区间内H半素数的个数。

     分析及代码:

    模仿素数筛的写法,很容易实现O(nlogn)的H素数筛法。

    在筛选过程中,将H数为标记为三种类别,素数、半素数、合数。

    最后将所有含有半素数标记的取出,采用upper_bound二分查找可以快速得到答案。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn = 1000100;
    int Htype[maxn];    // 0:prime  2:semi-prime  1:composite
    //int p[maxn], num;
    int pp[maxn], num2;
    void init() {
    //    Htype[1] = isp[5] = 0;
        for(int i=5;i<maxn;i+=4) {
            if(!Htype[i]) {
                //p[num++] = i;
                if(i<maxn/i) Htype[i*i] = 2;
    
                for(int j=2*i;j<maxn;j+=i) {
                    if(j%4==1) {
                        Htype[j] = 1;
                        if((j/i)%4==1 && !Htype[j/i])
                            Htype[j] = 2;
                    }
                }
            }
        }
    
        for(int i=5;i<maxn;i+=4) {
            if(Htype[i]==2) {
                pp[num2++] = i;
            }
            
        }
    }
    
    int main()
    {
        init();
        int h;
        while(scanf("%d", &h)!=EOF && h) {
            printf("%d ", h);
            printf("%d
    ", upper_bound(pp, pp+num2, h) - pp);
        }
        return 0;
    }
    View Code

        (未完待续)

  • 相关阅读:
    frame框架 超链接
    SQL与ASP日期时间
    J2EE开发环境搭建(3)——保存你搭建好的J2EE开发环境
    td.moveRow方法
    SQL Management Studio Express 安装缺少MSXML6解决
    js tr onmouseover时改变该行背景色
    doc 解决windows系统中名字为eula的文件无法被删除
    js select 隐藏option
    LLBLGen的数据库相对应SQL语句实现方法收藏
    使用 DataAdapter 更新数据源
  • 原文地址:https://www.cnblogs.com/izcat/p/11161269.html
Copyright © 2011-2022 走看看