鱼C论坛

 找回密码
 立即注册
查看: 2419|回复: 2

题目160:阶乘数字的结尾数字

[复制链接]
发表于 2016-9-14 16:20:29 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Factorial trailing digits

For any N, let f(N) be the last five digits before the trailing zeroes in N!.
For example,

      9! = 362880 so f(9)=36288
    10! = 3628800 so f(10)=36288
    20! = 2432902008176640000 so f(20)=17664

Find f(1,000,000,000,000)


题目:

对任意的 N,令定义 f(N) 为 N! ( N 的阶乘)除末尾的 0 以外的最后 5 个数字,比如

      9! = 362880  所以  f(9)=36288
    10! = 3628800  所以  f(10)=36288
    20! = 2432902008176640000  所以  f(20)=17664

求 f(1,000,000,000,000)
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2020-5-8 22:12:13 | 显示全部楼层
本帖最后由 guosl 于 2022-10-16 11:16 编辑

我们用CUDA进行死算。
答案:16576
  1. /*
  2. 答案:16576
  3. 耗时:184.763907s(cmp30hx,最近矿卡非常便宜 )
  4. */
  5. #include <cstdio>
  6. #include <cuda_runtime.h>
  7. #include <device_launch_parameters.h>
  8. #include <helper_cuda.h>
  9. #include <omp.h>
  10. using namespace std;

  11. const long long N = 1000000000000LL;
  12. const int nStep = 46080 * 16; //分段的区间长度
  13. const int nBlocksSize = 128;
  14. const int nBlocks = (nStep + nBlocksSize - 1) / nBlocksSize;
  15. int* result_d = NULL;//存放结果的显卡内存
  16. int* result = NULL;//存放结果的主机内存

  17. int qpow(long long k)
  18. {
  19.   long long p;
  20.   if (k == 0)
  21.     return 1;
  22.   if (k == 1)
  23.     return 2;
  24.   if ((k & 1) == 0)
  25.   {
  26.     p = qpow(k >> 1);
  27.     p *= p;
  28.     p %= 100000;
  29.   }
  30.   else
  31.   {
  32.     p = qpow(k - 1);
  33.     p *= 2;
  34.     p %= 100000;
  35.   }
  36.   return (int)p;
  37. }

  38. __global__ void getpr(long long b, int *result)
  39. {
  40.   extern __shared__ long long nVal[];
  41.   nVal[threadIdx.x] = 1;
  42.   long long k = blockIdx.x * nBlocksSize + threadIdx.x + b;//当前的计算序号
  43.   if (k <= N)
  44.   {
  45.     long long p = k;
  46.     while ((p & 1) == 0)
  47.       p = p >> 1;
  48.     while (p % 5 == 0)
  49.       p /= 5;
  50.     p %= 100000;
  51.     nVal[threadIdx.x] = p;
  52.   }
  53.   __syncthreads();
  54.   //累乘本block内得到的值
  55.   for (int k = nBlocksSize >> 1; k > 0; k = k >> 1)
  56.   {
  57.     if (threadIdx.x < k)
  58.     {
  59.       nVal[threadIdx.x] *= nVal[threadIdx.x + k];
  60.       nVal[threadIdx.x] %= 100000;
  61.     }
  62.     __syncthreads();
  63.   }
  64.   if (threadIdx.x == 0)
  65.     result[blockIdx.x] = (int)nVal[threadIdx.x];//放入返回的变量中
  66. }

  67. int main(void)
  68. {
  69.   long long k = 1, ppr = 1;
  70.   long long c2 = 0, c5 = 0, n = N;
  71.   double t = omp_get_wtime();
  72.   cudaDeviceProp deviceProp;
  73.   int devID = 0;
  74.   cudaError_t cudaStatus = cudaSetDevice(devID);//设置计算卡
  75.   if (cudaStatus != cudaSuccess)
  76.     goto end;
  77.   cudaStatus = cudaGetDeviceProperties(&deviceProp, devID);
  78.   if (cudaStatus != cudaSuccess)
  79.     goto end;
  80.   printf("GPU Device %d: "%s" with compute capability %d.%d\n\n", devID, deviceProp.name, deviceProp.major, deviceProp.minor);
  81.   while (n > 1)
  82.   {
  83.     c2 += n / 2;
  84.     n = n >> 1;
  85.   }
  86.   n = N;
  87.   while (n > 1)
  88.   {
  89.     c5 += n / 5;
  90.     n /= 5;
  91.   }
  92.   c2 -= c5;
  93.   ppr = qpow(c2);
  94.   result = new int[nBlocks];
  95.   if (result == NULL)
  96.     goto end;
  97.   cudaStatus = cudaMalloc((void**)&result_d, nBlocks * sizeof(int));
  98.   if (cudaStatus != cudaSuccess)
  99.     goto end;
  100.   while (k <= N)
  101.   {
  102.     cudaStatus = cudaMemset(result_d, 0, nBlocks * sizeof(int));//将返回的数组清零
  103.     if (cudaStatus != cudaSuccess)
  104.       goto end;
  105.     getpr << <nBlocks, nBlocksSize, nBlocksSize * sizeof(long long) >> > (k, result_d);
  106.     cudaError_t cudaStatus = cudaGetLastError();
  107.     if (cudaStatus != cudaSuccess)
  108.       goto end;
  109.     cudaDeviceSynchronize();//等待计算完成
  110.                             //将区间和复制回主机内存
  111.     cudaStatus = cudaMemcpy(result, result_d, nBlocks * sizeof(int), cudaMemcpyDeviceToHost);
  112.     if (cudaStatus != cudaSuccess)
  113.       goto end;
  114.     for (int i = 0; i < nBlocks; ++i)
  115.     {
  116.       ppr *= (long long)result[i];
  117.       ppr %= 100000;
  118.     }
  119.     k += nStep;
  120.   }
  121.   cudaDeviceReset();
  122.   t = omp_get_wtime() - t;
  123.   printf("%lld\n%lf\n", ppr, t);
  124. end:
  125.   if (result != NULL)
  126.     delete[] result;
  127.   if (result_d != NULL)
  128.     cudaFree(result_d);
  129.   return 0;
  130. }
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-9-27 22:41:52 | 显示全部楼层
本帖最后由 guosl 于 2022-9-28 07:17 编辑

我们再给一种解法:
当n%5==0时有公式:n!=n-!*5^(n/5)*(n/5)!这个可以应用数学归纳法来证明。
其中n-!为普通阶乘中去掉所有5的倍数,例如:10-!= 1*2*3*4*6*7*8*9
由于10^(n/5)只产生一串零,故在计算中舍去。只需计算:n-!/(2^(n/5))*(n/5)!
对于n-!/(2^(n/5))的计算
我们把n个数分成若干段,每段有如下形式
(kM+0)*(kM+1)*...*(kM+4)*(kM+6)*...*(kM+9)*(kM+11)*...*(kM+i)*...*(kM+M-1)
这样的段有n/M段,而每段在%M的作用下都变成了
(1*...*4*6*...*9*11*...*i*...*(M-1))
整个计算变成了
(1*...*4*6*...*9*11*...*i*...*(M-1))^(n/M)
而根据扩展欧拉定理:i^(n/M)=i^(Phi(M)+(n/M)%Phi(M)),其中Phi()为欧拉函数
所以:f(n)=(g(n)*f(n/5))%100000
我们可以根据这个得到下面的递归计算
答案:16576,计算耗时可以忽略不记。
  1. #include <iostream>
  2. using namespace std;

  3. const long long N = 1000000000000LL;
  4. const int M = 100000, Phi_M = 40000;//Phi_M为M的欧拉函数值

  5. int qpow(int b, int n)//快速幂
  6. {
  7.   long long x = b;
  8.   if (n == 0)
  9.     return 1;
  10.   if (n == 1)
  11.     return int(x % M);
  12.   if ((n & 1) == 0)
  13.   {
  14.     x = qpow(b, n >> 1);
  15.     x *= x;
  16.     return int(x % M);
  17.   }
  18.   else
  19.   {
  20.     x = qpow(b, n - 1);
  21.     x *= b;
  22.     return int(x % M);
  23.   }
  24. }

  25. int g(long long n)//计算n-!/2^(n/5)的值
  26. {
  27.   //下面计算的依据是扩展欧拉定理
  28.   long long m1 = n / M;
  29.   if (m1 > Phi_M)
  30.     m1 = m1 % Phi_M + Phi_M;
  31.   long long m2 = (m1 + 1) / 2;
  32.   if (m2 > Phi_M)
  33.     m2 = m2 % Phi_M + Phi_M;
  34.   long long m3 = m1 / 2;
  35.   if (m3 > Phi_M)
  36.     m3 = m3 % Phi_M + Phi_M;
  37.   long long r = 1;
  38.   for (int i = 0; i < M; ++i)
  39.   {
  40.     if (i % 5 == 0)
  41.       continue;
  42.     //我们将2^(-n/5)分配到乘积中,具体就是个位数是2或4的数除个2
  43.     if (i % 10 == 2)
  44.     {
  45.       r *= qpow(int(i / 2), (int)m2);//处理其中除2的因子,这个要具体计算
  46.       r %= M;
  47.       r *= qpow(int((M + i) / 2), (int)m3);
  48.     }
  49.     else if (i % 10 == 4)
  50.     {
  51.       r *= qpow(int(i / 2), (int)m2);//处理其中除2的因子
  52.       r %= M;
  53.       r *= qpow(int((M + i) / 2), (int)m3);
  54.     }
  55.     else
  56.       r *= qpow(int(i), (int)m1);
  57.     r %= M;
  58.   }
  59.   return (int)r;
  60. }

  61. int f(long long n)
  62. {
  63.   long long r = 1;
  64.   if (n % M == 0)
  65.   {
  66.     r = g(n);
  67.     r *= f(n / 5);//递归
  68.     r %= M;
  69.     return (int)r;
  70.   }
  71.   //n的值已经减小到可以直接计算了
  72.   int c2 = 0;
  73.   {
  74.     int k = (int)n, c5 = 0;
  75.     while (k >= 2)
  76.     {
  77.       c2 += k / 2;
  78.       k /= 2;
  79.     }
  80.     k = (int)n;
  81.     while (k >= 5)
  82.     {
  83.       c5 += k / 5;
  84.       k /= 5;
  85.     }
  86.     c2 -= c5;
  87.   }
  88.   r = qpow(2, c2);
  89.   for (int i = 1; i <= n; ++i)
  90.   {
  91.     int k = i;
  92.     while ((k & 1) == 0)
  93.       k /= 2;
  94.     while (k % 5 == 0)
  95.       k /= 5;
  96.     k %= M;
  97.     r *= k;
  98.     r %= M;
  99.   }
  100.   return (int)r;
  101. }

  102. int main(void)
  103. {
  104.   cout << f(N) << endl;//调用递归计算
  105.   return 0;
  106. }
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-5-2 23:46

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表