题目详情:
给定表达式[x/2] + y + x * y, 其中x,y都是正整数。其中的中括号表示下取整,例如[3/2] = 1 , [5/2] = 2。
有些正整数可以用上述表达式表达出来,例如正整数2,当取x = y = 1时,可以把2表达出来
( 解释下:当x=y=1时, [x / 2] + y + x * y = [1 / 2] + 1 + 1 * 1 = 0+1+1 = 2 );
有些数可以有多种方式表达,例如13可以由 x = 2 y = 4 以及x = 3 y = 3来表示;
有些数无法用这个表达式表达出来,比如3。
从1开始第n个不能用这个表达式表示出来的数,我们叫做an,例如a1=1 a2=3,给定n,求an。
输入:n值 1<=n<=40
输出:an % 1000000007的结果(因为结果较大,输出an %1000000007的结果)。
方法一:
算法思想:
⒈将要表示的数记为count,则count = [x/2] + y + x * y
⒉将式子变形count = (x + 1) * y + x / 2 ↔ y = (count - x/2 ) / (x + 1) ①
⒊所以当我们对count取不同值时,判断是否可以被表示,即判断①式中y 对于 x取(1、2、3…(x / 2 + x + 1 <= count)
能否得到整数值( (count - x/2 ) % (x + 1) == 0),若对于所有x, y均得不到整数,则count不可表示
⒋当x取1时,count = 2 * y, 所以不可表示的数一定是奇数
⒌所以我们将count从1开始以2为增补量不断测试,得到第n个不可表示的数就输出
代码如下:
#include <stdio.h>
int givean(int n)
{
int i = 0, m;
int count = 1, x;
do
{
m = 1;
for (x = 1; x / 2 + x + 1<= count; x++)
{
if ((count - x/2) % (x + 1) == 0)
{
m = 0;
break;
}
}
//当count不可表示
if (m == 1)
i++;
count += 2;
}
while (i < n);
return (count - 2)% 1000000007;
}
int main()
{
int n, x;
scanf("%d", &n);
x = givean(n);
printf("%d\n", x);
return 0;
}
但是当我们运行时发现得到a1 = 1, a2 = 3, a3 = 15, a4 = 63, a5 = 4095, a6 = 65535, a7 = 262143很快。
当n = 8,算出(a8 = 1073741823) % 1000000007= 73741816却要两、三分钟。所以此方法理论是对的,却不合适。
相信很多人像我一样在,接下来是对上面算法进行各种优化,加大count的增补量、减小x的循环范围等等,但作用对于求解a40来说将微乎其微。
郁闷,疑惑之中在网上大查一番终于有所眉目,将借鉴整理如下。
方法二
下面仁兄博客将对此方法用到的数学方法,进行详细说明:
http://blog.csdn.net/oucyxc/article/details/9958035
相信看完以上博客我们知道:若一个数2^(m + 1)- 1是素数,则2^m - 1即为一个不可表示的数(m >= 1)
其实我们把2^p - 1为素数的数叫做梅森素数(百度可查)且对p的取值依次为:
2,3,5,7,13,17,19,31,61,89,
107,127,521,607,1279,2203,2281,3217,4253,4423,
9689,9941,11213,19937,21701,23209,44497,86243,110503,132049,
216091,756839,859433,1257787,1398269,2976221,3021377,6972593,13466917,20996011,
24036583,25964951,30402457,32582657,37156667,42643801,43112609,57885161
目前只发现48个梅森素数。
编程的思想是我们可以把前四十个p值存在一个数组里,比如求第n个不可表示的数,则取第n个p值(假设为k),
计算第n个不可表示的数(2^(k - 1) - 1)% 1000000007 ↔ 2^(k - 1) % 1000000007 - 1,
但是我们会遇到一个问题2^(k - 1) 肯定会出现溢出.
这时我们可以,利用一个公式(A * B)mod C = ((A mod C) * (B mod C)) mod C
可推(a * b * c * d)mod e(若b, c, d是小于c的)= (((a mod e)* b) mod e * c) mod e * d) mod e
可推2^m mod c = ( (2^(m - 1) mod c) *2) mod c = ((2^(m - 2) mod c * 2) mod c * 2) mod c(即乘以一次二取一次摸,多少次方重复多少次)
所以我们边取摸边计算就不会溢出
代码如下:
#include <stdio.h>
int givean(int n)
{
int p[]={
2,3,5,7,13,17,19,31,61,89,
107,127,521,607,1279,2203,2281,3217,4253,4423,
9689,9941,11213,19937,21701,23209,44497,86243,110503,132049,
216091,756839,859433,1257787,1398269,2976221,3021377,6972593,13466917,20996011
};
int num = p[n-1] - 1, s = 1, i;
for (i = 1; i <= num; i++)
{
s *= 2;
s = s % 1000000007;
}
return s-1;
}
int main()
{
int n;
scanf("%d", &n);
printf("%d\n", givean(n));
return 0;
}