欧拉计划题解整理(开课吧实训课作业)

各种基于数学知识的题目,有点绕,但是为了基础,还是硬着头皮写啦
家人们,先上链接!
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;
}

我的思路应该是和视频的思路基本一致,就是观察网格规律,得出最终结果

01111
12345
1361015
14102035
15153570

路径可选择的方式按如上规律排列,不断通过相邻的两个数字得到当前对应格子的数字

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变为参数传给函数使用,也算是比较常规的问题。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值