各种基于数学知识的题目,有点绕,但是为了基础,还是硬着头皮写啦
家人们,先上链接!
https://projecteuler.net/archives.
以下数字标号表示欧拉计划的题号
1. 1000以内3和5的倍数和
int a,b,c;
a= (3+999)*333/2;
b=(5+995)*199/2;
c=(15+990)*66/2;
cout<<a+b-c<<endl;
这个比较简单,1000以内3和5的倍数,用3的等差数列求和加上5的再减去公倍数15的就得出结果啦
2.4000000以内斐波那契数列中的偶数
int a=1,b=2,ans =2;
while(b<=4000000){
b+=a;
a=b-a;
if(b%2==0){
ans+=b;
}
}
cout<<ans<<endl;
这个计算思路有点类似于双指针,就是把两个变量作为遍历计算从而获取整个斐波那契数列,期间因为b在靠后的位置,所以通过b来判断是否为偶数,是则将结果累加给ans
4.最大回文数
bool huiwen(){
int a=0,b=n; //这里有点懵逼,不知道为什么a如果不初始化为0(就是不给他赋初值)就得不到正确结果,用debug模式也没看懂
while(n){
a=a*10+n%10;
n/=10;
}
return a==b;
}
int main(){
int ans = 0;
for(int i=100;i<1000;i++){
for(int j=i;j<1000;j++){
int n = i*j;
if(huiwen(n))
ans = max(n,ans);
}
}
cout<<ans<<endl;
}
回文数,把一个数字反转就是通过模运算和除以10
6.一百个自然数的和的平方与平方和的差
long long ans = 5050*5050;
for(int i=1;i<=100;i++){
ans += (50+i)*(50-i);
}
ans -= 100*2500;
cout<<ans<<endl;
这个没啥技术含量,就是纯数学计算
8.连续数字最大乘积
string str;
cin>>str;
long long ans = 1,zero_cnt = 0,//当前0的个数计数器,在后续判定是否需要与当前最大乘积比较时所用,因为一旦当前连乘的数字中有0,就不用比较了
now = 1;
for(int i=0;i<1000;i++){
if(i<13){
now *= str[i] - '0';//因为读取的是字符串,所以当前字符的值就需要减去‘0’的值便于获得
} else{
if(str[i] == '0'){
zero_cnt++;
} else {
now *= str[i]-'0';
} //上边的if else用于判断下一个乘数是否为0,下边的if else用于判断第一个乘数是否为0,如果为0
if(str[i-13] == '0') {
zero_cnt--;
} else {
now /= str[i-13] -'0';
}
}if(zero_cnt == 0)
ans= max(ans,now);
}
cout<<ans<<endl;
滑动窗口,值得注意的是这里窗口右端每出现一个0,当前窗口的数字就少一个,左端出现一个0,窗口的数字就多一个,因为出现的0最终会被左端右端各检测一次,所以一般窗口大小始终为13(到了末尾就不一定了)。这个思路比较麻烦,然后自己重新写了一个,理解起来比较简单。
string str;
cin>>str;
int num = 0;
long long ans = 1,now = 1;
for(int i=0;i<1000;i++){
if(str[i] == '0'){
now = 1;
num = 0;
} else{
if(num<13)
{
num++;
} else{
now /= str[i-13]-'0';
}
now *= str[i]-'0';
}
ans= max(ans,now);
}
cout<<ans<<endl;
这里 num 用于表示当前窗口有多少个数,
首先判断窗口右端是不是0,如果是,就直接把当前窗口个数置为0,从头开始建立窗口——当窗口中数字少于13个时,不断累乘,但是仍然要执行比较操作;如果是13个,则先除去窗口左端的值,再乘窗口右端的值
11.方阵中的最大乘积
int num[30][30],ans;
int dirx[4] = {-1,0,1,1};
int diry[4] = {1,1,1,0};
int main() {
for(int i=5;i<25;i++){
for(int j=5;j<25;j++){
cin>>num[i][j];
}
}
for(int i=5;i<25;i++){
for(int j=5;j<25;j++){
for(int k=0;k<4;k++){ //开始进行4个方向的判断
int tmp = num[i][j];
for(int l=1;l<4;l++){
int x = i+dirx[k]*l;
int y = j+diry[k]*l;
tmp *= num[x][y];
}
ans = max(ans,tmp);
}
}
}
cout<<ans <<endl;
return 0;
}
这个题思路其实比较简单,但是这里如果想用比较简单的方法还是需要花点心思,如果最笨的方法其实还是类似于使用滑动窗口法,将一个4*4的矩阵定为窗口,矩阵【0,0】的数字作为移动标准,不断向后向下移动,从每个矩阵的最大值中选取最大值,但是这个过程在写代码时比较麻烦,最后放弃了,视频中的思路是方向数组,就是将一个数字向多个方向移动:
正常来说有正向加偏向总共8个方向,但是因为顺序不影响最终大小,所以只选取4个方向,这里通过在输入过程的小技巧,将原本的正常25x25的输入数组放置在了30x30中的数组中,则相当于在输入数组外层包围了一些为0的值,这样输入数组边界部分的数字就恰好不会出现越界计算的问题。
int dirx[4] = {-1,0,1,1};
int diry[4] = {1,1,1,0};
这两个数组的组合就是方向数组,就是实际输入数字变化时的下标变化量
14.最长考拉兹序列
int num[10000005]; //记忆化数组
int func(long long n){ //递归函数
if(n == 1)
return 1;
if(n<10000000&&num[n]!=0){ //保证数字不越界并且未被标记过,因为有些数字在计算过程中可能会被多个数字重复标记
return num[n];
}
int tmp = 0;
if(n%2==0){
tmp= func(n/2)+1;
} else{
tmp = func(3*n+1)+1;
}
if(n<10000000)
num[n] = tmp;
return tmp;
}
int main() {
int ans = 1;
for(int i=1;i<=1000000;i++) {
if(func(ans)<func(i)){
ans = i;
}
}
cout<<ans<<endl;
}
这个过程使用递归思路很简单,但是这里额外使用了一个数组,称为记忆化数组,用于存储递归过程中产生的一些中间值,从而将一些重复的递归过程省去了,加快了程序的执行速度,大概意思就是递归+记忆化数组的效率接近于迭代,所以在使用递归过程中只要设定好一些条件,就可以使用记忆化数组提高程序执行效率,经典例题就是斐波那契数列。
15.网格路径
long long arr[21][21];
int main(){
for(int i=0;i<21;i++){
arr[0][i]=1;
}
for(int i=1;i<21;i++){
for(int j=1;j<21;j++){
if(i==j)
arr[i][j] = arr[i-1][j]*2;
else
arr[i][j] = arr[i-1][j]+arr[i][j-1];
}
}
cout<<arr[20][20]<<endl;
}
我的思路应该是和视频的思路基本一致,就是观察网格规律,得出最终结果
0 | 1 | 1 | 1 | 1 |
---|---|---|---|---|
1 | 2 | 3 | 4 | 5 |
1 | 3 | 6 | 10 | 15 |
1 | 4 | 10 | 20 | 35 |
1 | 5 | 15 | 35 | 70 |
路径可选择的方式按如上规律排列,不断通过相邻的两个数字得到当前对应格子的数字
18.最大路径和I
int num[20][20],dp[20][20];
int main() {
for(int i=1;i<=15;i++){
for(int j=1;j<=i;j++) {
cin >> num[i][j];
}
}
int fin = 0;
for(int i=1;i<=15;i++){
for(int j=1;j<=i;j++) {
dp[i][j] = max(dp[i-1][j-1],dp[i-1][j])+num[i][j];
fin = max(fin,dp[i][j]);
}
}
cout<<fin<<endl;
}
25.1000位斐波那契数
int func(int *n1,int *n2){ //大整数加法
n2[0] = n1[0];
for(int i=1;i<=n2[0];i++){
n2[i]+=n1[i];
if(n2[i]>9){
n2[i+1] += n2[i]/10;
n2[i]%=10;
if(i ==n2[i]){
n2[0]++;
}
}
}
return n2[0] == 1000;
}
int main() {
int num[2][1005]={{1,1},{1,1}}; //num[0][0]和num[1][0]都是用来存储数字位数的
int a=0,b=1;
for(int i=3;1;i++){
if(func(num[a],num[b])){
cout<<i<<endl;
break;
}
swap(a,b);
}
}
每次相加完将结果存储在b中,然后交换a,b,相当于不断替换存储相加结果的变量,计算过程就是大数相加(高精度运算)。
30.各位数字的五次幂
int num[10];
void init() {
for(int i=1;i<=9;i++){
int t = i;
for(int j=1;j<5;j++){
t *= i;
}
num[i] = t;
}
}
int func(int x){
int t=0,raw =x;
while(x){
t += num[x%10];
x/=10;
}
return t==raw;
}
int main() {
init();
int ans = 0;
for(int i=10;i<1000000;i++){
if(func(i)){
ans+=i;
}
}
cout<<ans<<endl;
}
因为这里需要计算每个位数的5次幂,就相当于计算0-9的5次幂,所以提前用一个数组算好了便于使用,剩下的过程就是遍历所有数字,判断是否可以满足条件,这里i的上限是根据10y = 95 *y,解方程得y<6,则这里最大为106
32.全数字的乘积
#include <cmath>
int digit(int n){
return floor(log10(n))+1;
}
int check(int x,int *num){
while(x) {
int t=x%10;
x /= 10;
if(num[t] == 1){
return 0;
}
num[t] = 1;
}
return 1;
}
int func(int a,int b,int c) {
int num[10] = {1};//这里仅将num[0]赋值为1,其余为0,为了避免0对全数字判断出现干扰
if(check(a,num)&&check(b,num)&&check(c,num)){
return 1;
}
return 0;
}
int main() {
int ans = 0,mark[10005] = {0};
for(int i=1;i<=98;i++) { //第一个乘数
for(int j=i+1;1;j++) { //第二个乘数,也就是被乘数,这里的考虑是99*100=9900已经是9个位数了,所以
//最多也就是一个两位数乘三位数得到一个四位数满足全数字的条件
int a=digit(i),b=digit(j),c=digit(i*j);//获取乘数,被乘数,乘积的位数
if(a+b+c>9){
break; //总位数超过9位就直接跳出这个循环,因为如果j再增加,位数仍然是超过9位
} else if(a+b+c == 9){
if(func(i,j,i*j)){ //如果恰好等于9,则判断当前的三个数是否为全数字
if(!mark[i*j]){ //同时判断完将乘积的标记修改,避免重复的乘积被加两次
ans += i*j;
mark[i*j]=1;
}
cout<<i<<"*"<<j<<"="<<i*j<<endl;
}
}
}
}
cout<<ans<<endl;
}
主要注意的事情在注释里写清楚了,需要注意的是digit这个函数可以在以后其他地方用,这道题思路明确很重要,就是先想明白获取全数字等式需要什么,结果就是两个数字相乘得到一个乘积,三个数字组成“全数字”,先考虑两个数字什么样子,再判断乘后的三个数字是否为全数字,最后相加时判定是否有重复项
33.消去数字的分数
int func(int a,int b) { //判断原分数和消去数字的分数是否相等,使用十字交叉相乘判断
int n1 = a / 10, n2 = a % 10;
int m1 = b / 10, m2 = b % 10;
if (!n2 || !m2)return 0;
if(n1==m1 && a*m2 == b*n2) return 1;
if(n1==m2 && a*m1 == b*n2) return 1;
if(n2==m1 && a*m2 == b*n1) return 1;
if(n2==m2 && a*m1 == b*n1) return 1;
return 0;
}
int gcd(int a,int b){ //递归法实现辗转相除,最终获得化简分数时使用
if(!b) return a;
return gcd(b,a%b);
}
int main() {
int a=1,b=1;
for(int i=11;i<100;i++){
for(int j=i+1;j<100;j++){
if(func(i,j)){
a*=i;b*=j;
cout<<i<<"/"<<j<<endl;
}
}
}
int c = gcd(a,b);
cout<<b/c<<endl;
}
纯粹一道思路题,不看视频真的没想到这么处理,看来这类题很多都是要把数字转换为单个的数字操作
36.双进制回文数
int func(int x, int n) {
int t = 0, raw = x;
while (x) {
t = t * n + x % n;
x /= n;
}
return t ==raw;
}
int main() {
int ans = 0;
for(int i=1;i<=1000000;i++){
if(func(i,10)&&func(i,2)){
ans += i;
cout<<i<<endl;
}
}
cout<<ans<<endl;
}
这里是要使用两种进制分别判断是否是回文数,其实本质上就是把十进制的10变为参数传给函数使用,也算是比较常规的问题。