东西终于写出来了,
唯有一句“痛快”可以表达做出来的感受。
做一半一直堵着是真难受啊。。。
这两天刷的题不是天梯,是Kevin突然提的问题,所以今天写的格式也稍稍不同啦。
开始是Kevin说要给我出一个题目:给你一个数n,让你求2^n的位数。
我:不给数据范围都是耍流氓。
Kevin:……n<10000。
开始的时候想着,假如2^(n-1)小于5000……00(k个0),那么2^n的位数就和2^(n-1)相同,否则就+1。
然后向子问题递归。
类似于分而治之的思想?也可能是我刚做完动态规划,自然而然就顺到这个节奏上了。
后来在Kevin的引导下,知道了思路原来是:
如果我找到一个数a,10^a<=2^n<10^(a+1)
那么求出这个a,再+1就是答案。
于是乎精简出来就只有一行代码:
a=n*log(2)/log(10)+1;
另外c++中无ln,log表示以e为底。语法形式为:log(10),表示以e为底10的对数。
这道题回味一下,你会有一种“还可以这样??”的感受。
Kevin说这个题是信息学竞赛的一个题目,也是今年清华软院夏令营的三道题中的一道题。
Kevin还说:
我让你做这道题就是想告诉你要抓住问题的本质,转换成数学或者基本算法去求解。
虽然我想不到。
感觉确实是不容易想到。。。
我以为这就结束了,没想到Kevin还有第二问在等着我。。。
就是要求2^n最后500位
最后整理一下问题,就变成了一道黄金段位的题目:麦森数
在解决麦森数之前,我们先来解决一个知识点,就是快速幂。
快速幂:
题目:
输入3个数a,b,c,求a^b mod c=?
输入描述:
三个数a,b,c
输出描述:
一个数,即a^b mod c 的答案。
样例输入:
5 10 9
样例输出:
4
数据范围及提示:
0<a,b,c<10000000000000000
我的答案:
其实快速幂还是挺简单的。
简单说就是:
假设a^b,b=11111
那么ans=a^(1)*a^(2)*a^(4)*a^(8)这样
然后呢a^2=(a^1)^2,a^4=(a^2)^2...
那么假设tmp=a,ans=1开始,则只需要计算五次,每次都是tmp=tmp^2,ans*=tmp
当b=10001的时候,就是存在0的时候,我计算到那一位就可以让ans不乘tmp,ans还是等于ans。
说白了就三句话:
1.temp不断平方
2.用b的二进制判断用不用乘
3.在ans上不断*temp
标准格式如下,实在不行背下来也不麻烦
int pow(int a,int b) //a^b
{
int res=1,arr=a;
while(b!=0)
{
if(b%2) res*=arr;
arr*=arr;
b/=2;
}
return res;
}
而快速取模就更好理解了,就是随便你什么时候取模都不会影响结果。
那么千万别攒在一起取模,每做一轮乘法就取模。
代码:
#include <iostream>
#define ll long long
using namespace std;
ll pow(ll a,ll b,ll c)
{
ll res=1;
a%=c;
while(b!=0)
{
if(b%2) res*=a;
a*=a;
b/=2;
a%=c;
res%=c;
}
return res;
}
int main()
{
ll a=0,b=0,c=0;
ll result;
cin>>a>>b>>c;
result=pow(a,b,c);
cout<<result;
return 0;
}
这道题比较尴尬的是,假如你所有样例都通过了,那么恭喜你,你做错了。
有一个测试样例是有问题的。
这组样例是酱婶儿的:
-
9946531947 6644974612 999999999
- 正确的输出应该是:
-
83371113
- 然而,给到的结果却是:
-
976737430
问题就出在:
网站管理员也没有意识到
64位机下,c语言内置的long long为64位,
而无符号的long long上限为2的64次方-1=18446744073709551615
当不进行任何处理就进入计算时,在第一步中
a*a=9946531947*9946531947=98933497772691610809 > 18446744073709551615
a的平方超出了这串电话号码一样的上限,
内存中存放的是补码,越界后应该为下限+超过的部分(我理解是这样)。从而最后产生了错误答案。
解决方法很简单,上来就让a对c取模,这样就算后续平方也没有问题。
我终于如愿以偿的拿到了光荣的90分。。。
解决了这一题
就要来看大boss麦森数了
麦森数:
题目:
形如2^P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2^P-1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。
任务:从文件中输入P(1000<P<3100000),计算2^P-1的位数和最后500位数字(用十进制高精度数表示)
输入描述:
文件中只包含一个整数P(1000<P<3100000)
输出描述:
第一行:十进制高精度数2^P-1的位数。
第2-11行:十进制高精度数2^P-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)
不必验证2^P-1与P是否为素数。
样例输入:
1279
样例输出:
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
我的答案:
这题第一问我们在上面已经解决了,2^p与2^p-1位数应该是一样的
第二问首先涉及到500位,要做到的必然就是高精度。
于是首先出现了我的第一版代码:
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
int arr[510];
void multip(int arr[],int p) //乘1次2
{
int i=0,j=0;
for(i=0;i<p;i++)
{
for(j=0;j<500;j++)
{
arr[j]=arr[j]*2;
}
for(j=0;j<500;j++)
{
arr[j+1]=arr[j+1]+arr[j]/10;
arr[j]=arr[j]%10;
}
}
arr[0]--;
j=0;
while(arr[j]<0)
{
arr[j]=arr[j]+10;
j++;
arr[j]--;
}
for(i=499;i>=0;i--)
{
cout<<arr[i];
if(i%50==0) cout<<endl;
}
}
int main()
{
int p=0,a=0;
int len=0;
int i=0;
cin>>p;
a=p*log(2)/log(10)+1;
cout<<a<<endl;
arr[0]=1;
multip(arr,p);
return 0;
}
我称之为:“疯狂取幂版”。
总之就是疯狂乘2,然后存进高精度数组里。
结局就是就只通过了一部分样例,因为时间复杂度太太太高了。。。
然后我就开始了我的曲线救国路线。
在Kevin的指导下,先学习了快速幂,然后思考了一下快速幂和高精度怎么结合,然后放弃了。
emmm,我觉得我目前的弱鸡水平,还是解决不了这么高难度的问题。
不做了。。。
不行,我好蓝瘦。。。
做也不是,不做也不是。。。
啊。。。好难受。。。
然后我就躺在床上难受的滚来滚去。。。
恨Kevin。。。
因为就是他给我出的这道题,让我陷入了两难的境地。
于是我把x战警看完了就睡觉了。。。
今天一早睡了个懒觉就来改代码。
平心静气带着样例,一点一点的按程序手动走了一下。
果然找到了我的智障bug,然后把有问题的部分重新敲了敲。
就出现了接下来的:“终于AC版”
然后就,AC了!?!?!?
激顿!
要注意的小细节:
1.开始的时候怎么就没想到把高精度数组作为快速幂的参数。。。
以后就把高精度数组当成正常的数吧,只不过需要重新定义加减乘除规则,其实和int型,float型都是一样的。
2.要注意高精度的乘法中,承接结果的数组要初始为0,因为高精度乘法不是覆盖,而是在其上进行加法
3.高精度乘法中承接结果的数组开大一点!因为运算过程中会出现i+j啊,至少要是乘数位数的二倍!
4.注意注意注意,输出格式!
下面就是我的 “终于AC版” 啦,啦啦啦。
#include <iostream>
#include <math.h>
#include <string.h>
using namespace std;
int arr[510];
int res[510];
int temparr[1000];//这两个开大一点,因为下面会出现i+j=1000的情况
int tempres[1000];
void multip(int a[],int b[],int c[]) //高精度乘法
{
int i=0,j=0,k=0;
for(i=0;i<500;i++)
{
for(j=0;j<500;j++)
{
c[i+j]=c[i+j]+a[i]*b[j];
}
}
for(k=0;k<500;k++)
{
c[k+1]=c[k+1]+c[k]/10;
c[k]=c[k]%10;
}
}
void pow(int arr[],int res[],int p) //快速幂(参数为高精度)
{
int i=0;
while(p!=0)
{
if(p%2)
{
multip(res,arr,tempres);
for(i=0;i<500;i++)
{
res[i]=tempres[i];
tempres[i]=0;
}
}
multip(arr,arr,temparr);
for(i=0;i<500;i++)
{
arr[i]=temparr[i];
temparr[i]=0;
}
p/=2;
}
}
int main()
{
int p=0,a=0;
int len=0;
int i=0,j=0;
int flag=0;
cin>>p;
a=p*log(2)/log(10)+1; //输出位数
cout<<a<<endl;
arr[0]=2;
res[0]=1;
pow(arr,res,p);
res[0]--; //减1 (可能有借位)
j=0;
while(res[j]<0)
{
res[j]=res[j]+10;
j++;
res[j]--;
}
for(i=499;i>=0;i--) //输出,注意输出格式
{
cout<<res[i];
if(i%50==0) cout<<endl;
}
return 0;
}
今天真的很荣幸可以AC掉Mason这道题目。
感谢Kevin,感谢codevs,感谢Devc++,感谢每一位支持我的字符,感谢在座的库函数和测试样例。
是你们的帮助和鼓励让我完成了这个难题。
我们下次再见~
(鞠躬)
(台下:掌声+撒花)