zoukankan      html  css  js  c++  java
  • KeepCode1 解题思路及代码实现

      本次题目皆来源于 HDUOJ 题目编号:

    IDOriginTitle
    Problem A HDU 4475 Downward paths
    Problem B HDU 4476 Cut the rope
    Problem C HDU 1576 A/B
    Problem D HDU 1060 Leftmost Digit
    Problem E HDU 1066 Last non-zero Digit in N!

      5个题目都属于思维题,且都没有复杂的编码,但是对逻辑思维转换还是有一定的要求.  笔者希望大家能够多细心思考,注重严谨的逻辑推导。

      虽然题目不难也不易,但是Acm的队员们还是相当不错,二年级的唐仕首当其冲踩过了A,C两题,一年级的梁王凯也不赖第一个过了D,还有一个不错的应数一年级队员林巧生

    秒杀了E题(全场仅此一村呀),就连我们的三年级学长 jian1573 也耐不住寂寞,陪大家练练,飘过了B题。

      持续1天04小时的趣味编程最终5个题都被AC,个人AC最多为3题。

      希望大家继续加油~~  

      A题: 对于拥有 n 层的三角塔,则从顶部到底部有 n+1 个不同的终点。 若定义 F ( n ) 为 n 层的三角塔从顶部移动到底部不同的方案总数 ,

    则,我们可以发现,当从第 n 层 走到 第 n+1 层的时候, 在第 n 层底部,有 n+1 个点可以 走向 n+1 层, 且每一个点有两条路走,所以我们可以得出

    F ( n+1 ) = F ( n ) * (n+1) * 2  , 通过求出生成函数,我们可以得出公式, F ( n ) = n! * 2^n % 1000003 ( n! 为N的阶乘,2^n 表示 2的n次方 )。

    虽然我们得出了 F(n)的表达式,但是 n 的取值为 10^18 ,O(n)的时间计算 n! 与 2^n ,无法在 1 s内解决,这个时候我们面临两个问题待解决:

      1.  n! * 2^n 数据超出 64bit 表示范围,溢出  2. n! * 2^n 直接计算,时间达不到要求

    对于问题1: 在这里,我们数学中有同余定理可以解决这个问题:  (a*b)% c =  [ ( a % c ) * ( b % c ) ] % c 

    通过这个性质,我们可以在计算 n! 与 2^n 的时候 不会溢出, 其峰值最大为 (c-1)^2 ,当且仅当 a, b取 c-1 时达到。

    对于问题2:首先观察 n! % c = 1*2*3*...*c*..*n % c 不难看出,当n >= c 时 n! %c 必定为0 ,则对于 n!, 我们只需要计算到 c , 在本题中 c = 1000003

    时间上还是能够接受。 再观察 2^n  =  2^(n/2) + 2^( n/2 ) + 2^( n %2 )    (其中 / 为整除) 我们可以通过二分快速幂在 O(logN)的时间内计算出 2^n。

    到此,两个问题已经解决,则我们所需要的答案就出来了。

      这里提及一下,对于 2^n 我们也可以不用每次计算, 和 n!的阶乘一样预处理计算出来后存储,之后O(1)时间即可。

    预处理解题代码:

    View Code
    #include <stdio.h>
    
    #define LL __int64
    
    const LL mod = 1000003;
    LL ans[mod + 5];
    
    int main()
    {
        int T;
        ans[0] = 1;
        for (LL i = 1; i <= mod; i++)
        {
            ans[i] = ans[i - 1] * (2 * i);
            ans[i] %= mod;
        }
        for (scanf("%d", &T); T--;)
        {
            LL n;
            scanf("%I64d", &n);
            if (n >= mod)
                puts("0");
            else
                printf("%I64d\n", ans[n]);
        }
        return 0;
    }

    使用二分快速幂的代码:

    View Code
    #include<stdio.h>
    #include<math.h>
    #include<string.h>
    
    const int mod = 1000003;
    typedef long long LL;
    
    LL tmp[mod+10];
    void init(){
        tmp[0] = 1;
        for(int i = 1; i <= mod; i++)
        {
            tmp[i] = tmp[i-1]*i%mod;
        }
    }
    LL Pow( LL x , LL n )
    {
        LL res = 1;
        while( n ){
            if(n&1) res *= x, res %= mod;
            x = x*x; x %= mod;            
            n >>= 1;
        }    
        return res;    
    }
    LL MUL(LL a, LL b)
    {
        LL c = 0;
        while( b ){
            if(b&1){
                if( (c += a) >= mod ) c -= mod; 
            }
            a <<= 1; if( a >= mod ) a -= mod;
            b >>= 1;    
        }
        return c;
    }
    int main(){
        init();
        int T; scanf("%d", &T);    
        LL n;
        while( T--)
        {
            scanf("%lld", &n);
            LL ans = 0;
            if( n < mod )    
                ans = MUL( Pow(2,n), tmp[n] );        
            printf("%lld\n", ans); 
        }
        return 0;
    }

      

      B题:依据题意,每条绳子最多截断一次。 

        定义 Count( x ) : 长度为 x 的绳子的数量。 Sum( x ) : 大于等于x的绳子的数量。  

        则 answer = MAX { 2*Count(i) + Sum( i/2 ) }  ( 其中 i 为所有不同的绳子长度)

        对于计算,我们可以在输入的时候就处理出Count( x ) , 然后O(n)枚举所有出现的长度的时候再用 树状数组或线段数 O( Log(N) ) 来统计。

        总体时间复杂度为 O(N*log(N) )

      这里给出 树状数组的解题代码:

        

    View Code
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<math.h>
    #define MIN(a,b) (a)<(b)?(a):(b)
    #define MAX(a,b) (a)>(b)?(a):(b)
    
    const int N = 100110;
    int rope[N], n, C[N], num[N];
    int Lowbit( int x ){
        return x&(-x);
    }
    void update( int x ){
        while( x <= N ){
            C[x] += 1;
            x += Lowbit(x);    
        }
    }
    int read( int x ){
        int res = 0;
        while( x >= 1 )
        {
            res += C[x];
            x -= Lowbit(x);
        }
        return res;
    } int main()
    {
        int T;
        scanf("%d", &T);
        while( T-- ){
            scanf("%d", &n);
            memset( C, 0, sizeof(C));        
            memset( num, 0, sizeof(num));    
            int max = 0;    
            for(int i = 1; i <= n; i++)
            {
                scanf("%d", &rope[i] );
                update( rope[i] );    
                max = MAX( max, rope[i] );    
                num[rope[i]]++;    
            }    
            int ans = n;
            for(int i = 1; i <= max; i++)
            {
                if( num[i] > 0 ){    
                    int x = ceil( i/2. );            
                    //printf(" x = %d\n", x );    
                    ans = MAX( ans , (read(max)-read(x-1))+num[i] );
                }
            }    
            printf("%d\n", ans );        
        }
        return 0;
    }

        其实也可以全部预处理出来,然后利用辅助数组存储后输出,这里给出jian1573的解题代码

    View Code
    using namespace std;
    const int N = 100010;
    int a[N],s[N];
    int main()
    {
        int T, n, x;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d",&n);
            memset(a,0,sizeof a );
             memset(s,0,sizeof s );
            for(int i=0;i<n;i++)
            {
                scanf("%d",&x);
                a[x]++;
            }
            for(int i=1;i<N;i++)
                   s[i] = s[i-1]+a[i];
            int ans = 0;
            for(int i=1;i<N;i++)
                if(a[i])
                    ans=ans>(a[i]+n-s[(i-1)>>1])?ans:(a[i]+n-s[(i-1)>>1]);
            printf( "%d\n",ans );
        }
        return 0;
    }

        

      C题

        解法一: 依据题意,方便表示,我们令 C = 9973 ,则有

        A % C = n  

        A % B = 0 

        则

        A = k1*C + n  

        A / B = k2*C + x  其中( k1, k2 为自然数, 0 <= x < C ) 

        转换一下得   A = k1*C + n = B * ( k2*C + x )    ==>  k1*C = B*k2*C + ( B*x-n ) 

        可得 (B*x - n ) % C = 0 

        这里B ,n皆为已知量,x的取值范围为 [ 0, 9973 )  ,所以我们可以枚举 x 的值即可。

      参考代码:

    View Code
    #include<stdio.h>
    typedef long long LL;
    const int mod = 9973;
    
    int main()
    {
        int T;
        LL n, B;
        scanf("%d", &T);    
        while( T-- && scanf("%I64d%I64d", &n,&B) ){
            for(int x = 0; x < mod; x++)
                if( (B*x - n) % mod  == 0 )
                {
                    printf("%d\n", x );    
                    break;    
                }
        }
        return 0;
    }

        解法二: 其实对于此题,我们还可以通过转换成线性同余方程 ( a*x + b*y = c )来求解:   关于线性同余方程的解法以及扩展GCD的证明请浏览笔者QQ空间的日志。

       对于  A % C = n

              A  % B = 0

       则  n = A - ( A/C ) *C  ( A/C 为计算机的整除),  设 x = A / B  则 A = B*x

       ==>  B*x + ( A/C ) *C = n 

      令 a = B, b = A/C , c = n 

      得到线性同余方程    ax + by = c

      使用扩展GCD求出 符合 a * x0 +b * y0 = 1的一组合法解, 可得 x = x0*c + b*k 其中k属于自然数  

      这里给出参考代码:

    View Code
    #include<stdio.h>
    #include<iostream>
    using namespace std;
    typedef long long LL;
    
    LL exgcd( LL a, LL b, LL &x, LL &y)
    {
        if( b == 0 )
        {
            x = 1; y = 0;
            return a;
        }
        LL d = exgcd( b, a%b, x, y );
        LL t = x;
        x = y;    
        y = t - (a/b)*y;
        return d;
    }
    
    LL gcd( LL a, LL b )
    {
        return b == 0 ? a : gcd( b, a%b );
    }
    
    void solve( int n, int B)
    {
        LL a  = B, b = 9973, c = n;
        LL x, y;
        exgcd( a, b, x, y );
        //printf("x = %lld, y = %lld\n", x, y );    
        LL ans = x*c%9973;
        while( ans < 0 ) ans += b;
        printf("%lld\n", ans );
    }
    int main()
    {
        int T;
        scanf("%d", &T);
        while( T-- )
        {
            int n, B;
            scanf("%d%d", &n,&B);    
            solve( n, B );
        }
        return 0;
    }

      D题: 设 M = N^N , 等式两边去对数 log10 

         log10 ( M ) = log10 ( N^N ) = N * log10 ( N ) 

         再化简过去 可得:  M = 10 ^(  N*log10(N) ) 

         我们假设 N * log10( N )  = A + B     (其中A为整数部分,B为小数部分)

         则有 M = 10^A + 10^B ,  对于整数A, 则10^A必定为 100...0的形式, 而对于10^B ,因为B = [0,1) 所以10^B 属于 [ 1,10 )区间 

         可以得出,对与M的最左边有影响的只有 10^B ,所以结果为   pow( 10 ,  N*log10(N) - floor( N*log10(N) ) ) 

      解题代码:

    View Code
    #include<stdio.h>
    #include<math.h>
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while( T-- )
        {
            double n, ans;
            scanf("%lf", &n);
            double tmp = n*log10(n);  
            tmp = tmp - floor(tmp);
            ans = pow(10, tmp);
            printf("%.0lf\n", floor(ans) );
        }
        return 0;
    }

      

      E题:对于 N !  = 1 * 2 * 3 * ... * n  

        当且仅当成对的出现2,5时,才会出现0。

        我们可以将 N!  的所有 2,5因子成对的去掉后,即最后一位必定是一个非0。

        那么接下来我们就需要求的是  去掉了成对的2,5后的 N!的最后一位

        为了方面,我们用M!表示  N!去掉了 成对的2,5的数值

        则我们需要的结果就是 M!%10, 通过同余定理,即可得出答案

        这里给出 12级 应数林巧生的AC代码

     

    View Code
    #include<stdio.h>
    #include<string.h>
    int mod[20]={1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2};
    int a[1000];
    char n[1000];
    int main()
    {
     int i,c,t,length;
        while(scanf("%s",n)!=EOF)
     {
      t=1;
      length=strlen(n);
      for(i=0;i<length;i++)
       a[i]=n[length-1-i]-'0';
      while(length)
      {
       length-=!a[length-1];
       t=t*mod[a[1]%2*10+a[0]]%10;
       for(c=0,i=length-1;i>=0;i--)
        c=c*10+a[i],a[i]=c/5,c%=5;
      }
      printf("%d\n",t);
     }
     return 0;
    }

         

         引用下leemars的报告:

    这道题要求N!的最后一个非0数字是多少,如果用一般作法,先统计2和5的个数,然
    后补乘2,得到的将是TLE。所以还需要再做简化:

    为了把0去掉,我们把所有的因数2和5都提出来,放到最后再处理。N!中的N个相乘的
    数可以分成两堆:奇数和偶数。偶数相乘可以写成(2^M)*(M!),M=N DIV 2。M!可以
    递归处理,因此现在只需讨论奇数相乘。考虑1*3*5*7*9*11*13*15*17* ... *N(如果
    N为偶数则是N-1),这里面是5的倍数的有5,15,25,35,... ,可以其中的5提出来
    ,变成(5^P)*(1*3*5*7*9* ... ),后面括号中共P项,P=(N DIV 5+1) DIV 2,而后
    面的括号又可以继续提5出来,递归处理。现在剩下的数是1 * 3 * 7 * 9 * 11 * 13
    * 17 * 19 * ... 。这些数我们只需要他们的个位数,因为(1 * 3 * 9 * 11 * 13
    * ... ) MOD 10 = (1 * 3 * 7 * 9 * 1 * 3 * ... ) MOD 10。我们列出余数表,
    1 3 1 9 9 7 9 1 1 3 1 9 9 7 9 ……。我们发现每八项MOD 10的结果是一个循环。
    算出奇数的结果后,我们再回头看统计了多少个2和5需要乘入。把2和5配对完都是N
    !后面的0,看剩下的2有几个。如果有剩下的2,考虑2^N的个位数又是2 4 8 6 2 4
    8 6 ……每四项一个循环,找出这个个位数后,和前面的结果相乘,再取个位数就是
    答案。由于我们完全把2和5的因素另外处理,所以在所有的乘法中,都只需要计算个位数乘法,并且只保留个位数的结果。

    但让我很惊异的是:为什么我提交总是WA?后来我才知道,原因是这道题的N相当大
    !达到了10^100!要用大数来处理!GPC四项编译开关全关的,自然查不出来!而且
    上面这个算法换成大数后会很麻烦。还有什么别的好方法吗?

    答案是有的。我们设F(N)表示N!的尾数。

    先考虑简单的。考虑某一个N!(N < 10),我们先将所有5的倍数提出来,用1代替原来
    5的倍数的位置。由于5的倍数全被提走了,所以这样就不会出现尾数0了。我们先把
    0..9的阶乘的尾数列出来(注意,5的倍数的位置上是1),可以得到table[0..9] =
    (1, 1, 2, 6, 4, 4, 4, 8, 4, 6)。对于N < 5,直接输出table[N]即可;对于N >
    = 5,由于提出了一个5,因此需要一个2与之配成10,即将尾数除以2。注意到除了0
    !和1!,阶乘的最后一个非零数字必为偶数,所以有一个很特别的除法规律:2 / 2
    = 6,4 / 2 = 2,6 / 2 = 8,8 / 2 = 4。比较特殊的就是2 / 2 = 12 / 2 = 6,
    6 / 2 = 16 / 2 = 8。这样我们就可以得到如下式子:
    代码:

          table[N]
    F(N) = ------------ (0 <= N < 10)
          2^([N/5])

    再考虑复杂的。考虑某一个N!(N >= 10),我们先将所有5的倍数提出来,用1代替原
    来5的倍数的位置。由于5的倍数全被提走了,所以这样就不会出现尾数0了。我们观
    察一下剩下的数的乘积的尾数,通过table表,我们发现这10个数的乘积的尾数是6,
    6 * 6的尾数还是6,因此我们将剩下的数每10个分成一组,则剩下的数的乘积的尾数
    只与最后一组的情况有关,即与N的最后一位数字有关。由于我们把5的倍数提出来了
    ,N!中一次可以提出[N/5]个5的倍数,有多少个5,就需要有多少个2与之配成10,所
    以有多少个5,最后就要除以多少个2。注意到除2的结果变化是4个一循环,因此如果
    有A个5,只需要除(A MOD 4)次2就可以了。A MOD 4只与A的最后两位数有关,很好求
    算。剩下的5的倍数,由于5已经全部处理掉了,就变成[N/5]!。于是,我们可以得到
    一个递归关系:
    代码:

          F([N/5]) * table[N的尾数] * 6
    F(N) = ----------------------------------- (N > 10)
              2^([N/5] MOD 4)

    这样我们就得到了一个O(log5(N))的算法,整除5可以用高精度加法做,乘2再除10即
    可。整个算法相当巧妙,写起来也比较轻松。

     因为 2^N 是以4为循环节的

    而且table[N]是以10为循环节的

    所以从10开始

         F([N/5]) * table[N的尾数] * 6
    F(N) = ----------------------------------- (N > 10)
              2^([N/5] MOD 4)

    右边的式子除了F[n/5]外 是以20为循环节的

    写出循环的末尾数字mod[20]={1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2}

         此为笔者对于这些题的解题思路. 时间仓促,加之笔者能力有限,有错误的地方忘指出.     

  • 相关阅读:
    Collections.unmodifiableMap,Collections.unmodifiableList,Collections.unmodifiableSet作用及源码解析
    Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
    Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例
    Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)
    Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码
    Cannot find class: com.mysql.jdbc.Driver错误及解决办法。
    Mybatis源码解析,一步一步从浅入深(一):创建准备工程
    Maven 创建项目之简单示例
    常用注解记录
    jmeter性能测试工具
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/2825095.html
Copyright © 2011-2022 走看看