不可表示的数(庞果网)完全解答

题目详情:

给定表达式[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,2099601
1,

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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值