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}

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

原文地址:https://www.cnblogs.com/yefeng1627/p/2825095.html