2014 年蓝桥杯 C 语言 B 组省赛第 3 题: 李白打酒

题目

标题:李白打酒

话说大诗人李白,一生好饮。幸好他从不开车。

一天,他提着酒壶,从家里出来,酒壶中有酒2斗。他边走边唱:

无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店5次,遇到花10次,已知最后一次遇到的是花,他正好把酒喝光了。

请你计算李白遇到店和花的次序,可以把遇店记为a,遇花记为b。则:babaabbabbabbbb 就是合理的次序。像这样的答案一共有多少呢?请你计算出所有可能方案的个数(包含题目给出的)。

注意:通过浏览器提交答案。答案是个整数。不要书写任何多余的内容。

题目分析

关于方案数量的问题往往都是使用深搜. 使用递归实现深搜可以使我们不必关心遍历的具体过程, 只需要把题目中给出的条件转换成程序语言即可.
根据题目信息, 变量有”店”, “酒”和”花”, 因此我们的递归函数必须包含这三个变量, 即:

void f(int dian, int hua, int jiu)...

根据”逢店加一倍,遇花喝一斗。”可以得出如下两个对函数 f() 自身进行调用的语句:

if(遇到店){
  f(dian-1,hua,jiu*2);
}

if(遇到花){
  f(dian,hua-1,jiu-1);
}

递归的边界是遇到了 5 次店, 10 次花, 并且最后酒喝光了, 即:

dian==0&&hua==0&&jiu==0

但是上面的分析过程 (对递归边界的定义) 是存在错误的. 我们根据上面的分析过程可以写出下面这个 (错误的) 程序:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int ans=0;

void f(int dian, int hua, int jiu){
        if(dian>0&&jiu>=0){
            f(dian-1,hua,jiu*2);
        }

        if(hua>0&&jiu>=1){
            f(dian,hua-1,jiu-1);
        }

        if(dian==0&&hua==0&&jiu==0){
            ans++;
        }

    }

int main(){

    f(5,10,2);
    cout<<ans<<endl;

    return 0;
}

上面这个程序的运行结果是:
27

“27”看上去也”像是”一个正确答案. 但是, 如果我们仔细分析题目就会发现, 上面这个程序没有满足题目中给出的下面这个条件:

已知最后一次遇到的是花,他正好把酒喝光了

也就是说, 李白最后一次遇到的是花, 而且在刚遇到的花的时候, 他的酒壶里还剩 1 斗酒, 随后”遇花喝一斗”把酒壶里面的酒都喝光了. 至此, 李白一共遇到了 5 次店, 10 次花, 并且喝完了全部的酒.

但是, 在上面的程序中, 计算的仅仅是李白”遇到了 5 次店, 10 次花, 并且喝完了全部的酒”, 存在李白最后连续遇到了两次花, 每次喝一斗, 最后把酒喝完的情况, 也存在李白最后连续遇到了四次花, 每次喝一斗, 最后把酒喝完的情况(题目中没有提到酒壶容量的上限).

为了满足这个”已知最后一次遇到的是花,他正好把酒喝光了”的条件, 我们先把最后一次遇到花并喝一斗酒的情况减去, 这样只需要计算李白遇到 5 次店, 9 次花, 并剩下了 1 斗酒有多少种情况就可以了, 正确的程序如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int ans=0; //定义全局变量用于计数

void f(int dian, int hua, int jiu){

/*模拟李白遇到店的情况
若要遇到店, 则剩下的店的数量必须大于 0
遇到店的时候, 剩下的花的数量不变但必须不为负数
遇到店之前不能把酒壶里面的酒喝成负数*/
        if(dian>0&&jiu>=0&&hua>=0){
            f(dian-1,hua,jiu*2);
        }

/*模拟李白遇到花的情况
若要遇到花, 则剩下的花的数量必须大于 0
遇到花的时候, 剩下的店的数量不变但必须不为负数
遇到花之前酒壶里面的酒至少要剩 1 斗以便于遇到花时喝*/        
        if(hua>0&&jiu>=1&&dian>=0){
            f(dian,hua-1,jiu-1);
        }

        if(dian==0&&hua==0&&jiu==1){
            ans++;
        }

    }

int main(){
    f(5,9,2);
    cout<<ans<<endl;
    system("pause");
    return 0;
}

本题正确答案:
14

2014 年蓝桥杯 C 语言 B 组省赛第 2 题: 切面条

题目

标题:切面条

一根高筋拉面,中间切一刀,可以得到2根面条。

如果先对折1次,中间切一刀,可以得到3根面条。

如果连续对折2次,中间切一刀,可以得到5根面条。

那么,连续对折10次,中间切一刀,会得到多少面条呢?

答案是个整数,请通过浏览器提交答案。不要填写任何多余的内容。

题目分析

本题其实可以不需要使用编程的方式解决, 这是一个数列找规律的问题. 对于找规律的问题需要记住的一点就是要手算出尽可能多的项, 这样找出来的规律才比较可靠 (题目中已经给出了数列的前三个值, 如果最终的规律可以靠前三个数列导出的话, 那么这道题就没什么考点了, 因此至少需要计算出数列中第 4 个数的值).

数列的规律可以从以下几个方面寻找:

  • 两个数值间的关系是否和两个数之间的差值有关系, 差值的变化是否具有某种规律, 例如呈指数增长的差值;
  • 前两个数的和 (或者差, 积, 商) 是否可以得出其后的数.

通过使用 Windows 系统中的”画图”工具绘制出前 4 种情况(如果时间充裕的话可以在得出结果后绘制第 5 种情况以验证对规律的猜测是否正确), 如图 1

图 1
图 1

由此, 我们可以得到关于面条个数的这样一个数列:

2, 3, 5, 9

接着我们可以得到这样一个规律:

2+0=2 (对折 0 次)
2+1=3 (对折 1 次)
3+2=5 (对折 2 次)
5+4=9 (对折 3 次)

进而得到:

2+2^0=3 (对折 1 次)
3+2^1=5 (对折 2 次)
5+2^2=9 (对折 3 次)

规律找到这里, 我们可以使用程序进行之后的计算, 程序如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int main(){
    int a=2;
    int ans=2;

    for(int i=0;i<=9;i++){
            ans=ans+pow(a,i);
    }
    cout<<ans<<endl;
    return 0;
}

程序运行结果:

1025

如果不知道 C/C++ 用于求次方的函数是什么, 也可以使用循环代替, 下面的程序同样可以计算出最终结果:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int main(){
    int ans=5;

    for(int i=2;i<=9;i++){
        int b=2;
        int a=2;
        for(int j=1;j<i;j++){
            b=b*a; //a 在循环的过程中必须始终为 2
        }
            ans=ans+b;
    }
    cout<<ans<<endl;
    return 0;
}

程序运行结果:

1025

在运行程序之前可以先设置断点进行单步调试, 看看程序运行的前几步是不是和我们手动计算出来的结果 (和规律) 一致.

另外, 正如本文开头所说的, 本题可以不通过编程的方式解决, 因为这可以看做是一个数学问题, 就是找数列的规律. 但是为什么我们上面找到数列的规律后还需要用程序计算呢? 因为上面得到的规律中, 等号左边的变量有两个, 每一步的计算都需要上一步的结果作为支撑 (当然, 也可以不编程, 直接借助系统中的计算器逐步计算, 这个方法也可以用于对程序计算结果的验证), 每一步的计算都不是独立的, 这样的规律显然不适合手算, 之前找到的规律如下:

2+2^0=3 (对折 1 次)
3+2^1=5 (对折 2 次)
5+2^2=9 (对折 3 次)

为了方便手算, 我们必须想办法去掉一个变量, 于是, 就有了下面这个规律:

1+2^0+2^0=3 (对折 1 次)
1+2^1+2^1=5 (对折 2 次)
1+2^2+2^2=9 (对折 3 次)

进而得到:

1+2*2^0=3 (对折 1 次)
1+2*2^1=5 (对折 2 次)
1+2*2^2=9 (对折 3 次)

进而又可得到:

1+2^1=3 (对折 1 次)
1+2^2=5 (对折 2 次)
1+2^3=9 (对折 3 次)

在上面的规律中, 对每次对折的求解都不依赖上一次对折得出的相关数值, 变量只有一个, 即对折的次数, 因此可以通过一次计算就得出对折 10 次后再在中间切一刀能够得到的面条个数, 计算过程与结果为:

1+2^10=1025

本题需要注意的一点是, 得到两根面条的时候 (第 1 次切的时候) 对折的次数是 0 次.
本题的关键是正确地找出数列中的多个数值, 如果只找出前三个数值则本题很可能会得出错误的结果.

2014 年蓝桥杯 C 语言 B 组省赛第 1 题: 啤酒和饮料

题目

标题:啤酒和饮料

啤酒每罐2.3元,饮料每罐1.9元。小明买了若干啤酒和饮料,一共花了82.3元。

我们还知道他买的啤酒比饮料的数量少,请你计算他买了几罐啤酒。

注意:答案是一个整数。请通过浏览器提交答案。

不要书写任何多余的内容(例如:写了饮料的数量,添加说明文字等)。

题目分析

这里使用使用循环暴力破解即可, 根据啤酒和饮料的价格以及一共花费了八十多块钱可以大致估计, 啤酒的数量不会超过 50 罐, 饮料的价格不会超过 60 罐, 由于有啤酒和饮料两个, 因此用两个嵌套的 for 循环对其进行遍历即可.

下面先来看一个有问题的程序.

下面这个程序在逻辑上是符合的, 但是无法运行出结果:

#include <iostream>
using namespace std;
int main(){
    for (int i=1; i<=50; i++){
        for (int j=1; j<=60; j++){
            if((i<j)&&(i*2.3+j*1.9==82.3)){
                cout<<i<<" "<<j<<endl;
            }
        }
    }
    return 0;
}

无法出结果的原因是, 如果参与运算的有浮点数, 那个其运算结果是不能用于比较是否相等的 (“==”两边不能是浮点数), 因为浮点数的精度不同可能导致两个本来相同的浮点数不相等.

正确的比较方法是计算两个数的差值, 如果差值小于一个极小的数就表明这两个数字是相等的, 正确的程序如下:

#include<iostream>
#include<cmath>
using namespace std;
int main(){
    for (int i=1; i<=50; i++){
        for (int j=1; j<=60; j++){
            if((i<j)&&abs((i*2.3+j*1.9) - 82.3)<0.0000000000001){
                //abs()库函数用于求绝对值
                cout<<i<<" "<<j<<endl;
            }
        }
    }
    return 0;
}

运行结果:

11 30

当然, 本题也可以通过将题目中给出的数据都扩大 10 倍, 将浮点类型转换成 int 类型之后再计算, 程序如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
    for(int pj=1;pj<60;pj++){
        for(int yl=1;yl<60;yl++){
            if(pj<yl&&pj*23+yl*19==823){
                cout<<"啤酒:"<<pj<<endl;
                cout<<"饮料:"<<yl<<endl;
            }
        }
    }
    return 0;
}

运行结果:

啤酒:11
饮料:30

其中 11 是啤酒的罐数且满足啤酒的罐数小于饮料的罐数 (可以在得出结果后使用 PC 中的计算器验证一下).
本题正确答案:
11

2013 年蓝桥杯 C 语言 B 组省赛第 3 题: 第39级台阶

题目

题目标题: 第39级台阶

小明刚刚看完电影《第39级台阶》,离开电影院的时候,他数了数礼堂前的台阶数,恰好是39级!

站在台阶前,他突然又想着一个问题:

如果我每一步只能迈上1个或2个台阶。先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步。那么,上完39级台阶,有多少种不同的上法呢?

请你利用计算机的优势,帮助小明寻找答案。

要求提交的是一个整数。
注意:不要提交解答过程,或其它的辅助说明文字。

题目分析

本题的正确答案是:

51167078

这里涉及递归, 斐波那契数列和动态规划, 可以使用深度优先搜索 (DFS) 的思想解决问题.

首先说一下深度优先搜索. 深搜的原则就是对每一个可能的路径都深入访问直到不能继续深入访问为止, 而且每个节点只访问一次. 深搜的应用条件是上一步的走法和下一步的走法的规则是一样的且该问题具备边界以结束深搜.

首先可以简化一下这个问题, 去掉题目中要求的”走偶数步”的限制. 之后, 剩下的问题就是每次上一个或者两个台阶, 一共有多少种走法, 第一步有两种可能(走一个台阶或者走两个台阶), 随后的每走过一步的下一步也都是有两种走法(走一个台阶或者走两个台阶).

假设函数 f(n) 可以计算在上面的条件下走完 n 阶台阶会有多少种走法, 则:

走完 1 个台阶之后 (走了 1 步), 剩余的走法有 f(n-1) 种;

走完 2 个台阶之后 (走了 1 步), 剩余的走法有 f(n-2) 种.

结束条件有两个, 一个是恰好走完了 39 个台阶, 另一个是走到了第 40 个台阶(正着走的情况下, 只剩下一个台阶时却迈了两步)或者走到了第 -1 个台阶(倒着走的情况下, 只剩下一个台阶的时候却迈了两步).

正着走: 可以认为是从楼梯下面往上面走;
倒着走: 可以认为是从楼梯上面往下面走;
正着走和倒着走的效果是一样的 (例如当”楼梯”是水平的”斑马线”的时候).

使用递归实现深搜的函数大致形式如下:

递归函数f(...){
    if(...){ //本层递归函数结束条件 1
        /* code */
    }else if (...) { //本层递归函数结束条件 2
        /* code */
    }else{
        递归函数f(...)//可能的步骤 1
        递归函数f(...)//可能的步骤 2
    }
}

在具体使用递归解决深搜问题的时候, 不同的思路和方法的最终实现方式会有些差别, 具体情况可以参考如下 4 个程序 (每个程序都可以独立解决”第39级台阶”这个问题).

程序 1 如下:

#include<iostream>
#include<stdio.h>
using namespace std;
int ans; //用于保存所有可能的走法
void f(int n, int step){//n: 剩下的阶梯数, step: 已走的步数

/*剩下的台阶数是负数 (如果最后只剩下一个台阶却走了两步
会导致产生剩下负数个台阶的情况), 这是不可能发生的,
因此退出 f() 函数.*/
    if(n<0){//判断边界条件, 函数出口 1

        return;
/*在 void 函数中使用"return"可以出发函数的强制结束,
类似于循环结构中的 "break", 在这里"return"用于退出
本层深度遍历, 回到上一个未被遍历的节点继续之后的深度遍历.
*/

    }
    if(n==0&&step%2==0){//判断边界条件, 函数出口 2
        ans++;
        return;
    }

/*尝试每一种可能的结果("n-1"和"n-2")并触发
下一步的递归操作 ("n-1,step+1"和"n-2,step+1")
*/
    f(n-1,step+1); //下一步可能的走法 1
    f(n-2,step+1); //下一步可能的走法 2
}
int main(){
    f(39,0);
    cout << ans << endl;
    return 0;
}

上面的程序是倒着计算(倒着走楼梯)的, 也可以按照正着计算(正着走楼梯)的方法写程序, 程序 2 如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int sum=0;

void f(int n, int step){

        if(n==39&&step%2==0){
            sum++;
            return;
        }

        if(n>39){
            return;
        }

        f(n+1,step+1);
        f(n+2,step+1);

    }

int main(){
    f(0,0);
    cout<<sum<<endl;
    return 0;
}

为了使逻辑上更清晰一些, 可以对上面的程序 2 做以下修改, 修改后得到的程序 3 如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int sum=0;

void f(int n, int step){

        if(n==39&&step%2==0){
            sum++;
            return;
        }else if(n>39){
            return;
        }else{
            f(n+1,step+1);
            f(n+2,step+1);
        }

    }

int main(){
    f(0,0);
    cout<<sum<<endl;
    return 0;
}

当然, 递归函数除了可以使用没有返回值的 “void f()” 来定义, 也可以使用有返回值的”int f()”来定义, 例如程序 4:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int sum=0;

int f(int n, int step){

        if(n==39&&step%2==0){
            sum++;
            return sum;
        }else if(n>39){
            return -1;
        }else{
            f(n+1,step+1);
            f(n+2,step+1);
            return 0;
        }

    }

int main(){
    f(0,0);
    cout<<sum<<endl;
    return 0;
}

2013 年蓝桥杯 C 语言 B 组省赛第 2 题 马虎的算式

题目

标题: 马虎的算式

小明是个急性子,上小学的时候经常把老师写在黑板上的题目抄错了。

有一次,老师出的题目是:36 x 495 = ?

他却给抄成了:396 x 45 = ?

但结果却很戏剧性,他的答案竟然是对的!!

因为 36 * 495 = 396 * 45 = 17820

类似这样的巧合情况可能还有很多,比如:27 * 594 = 297 * 54

假设 a b c d e 代表1~9不同的5个数字(注意是各不相同的数字,且不含0)

能满足形如: ab * cde = adb * ce 这样的算式一共有多少种呢?

请你利用计算机的优势寻找所有的可能,并回答不同算式的种类数。

满足乘法交换律的算式计为不同的种类,所以答案肯定是个偶数。

答案直接通过浏览器提交。
注意:只提交一个表示最终统计种类数的数字,不要提交解答过程或其它多余的内容。

题目分析

预备知识:
乘法交换律: a * b = b * a

该题目可以整理成以下几点:

  • 将三位数中间的数字拿出来放到两位数的中间, 更换前后两数的乘积相等.
  • 因为有 5 个不同的数字, 每个数字都可能取 1 到 9 不同的数字(不包括 0), 因此使用 5 层 for 循环, 将每个数字都从 1 尝试到 9, 看看是否满足 “ab * cde = adb * ce” 这样的形式.

使用编程解决问题的主要思路就是让程序一步步的满足所有题目给出的条件, 然后就可以通过一片片的循环得到最终的结果.

要满足的条件和对应的编程实现如下:

  • 需要有 5 个数字, 每个数字都是从 1 到 9 -> 5 个 for 循环
  • 5 个数字要各不相同 : if 判断, 每多出一个数字(每增加一层循环)都要判断这个数字是否与已有的数字相同, 只有与已有的数字不相同才继续向下循环.

解题程序如下:

#include<iostream>
#include<stdio.h> //不加这个不能使用 printf()函数
using namespace std;

int main(){
    int ans = 0;
    for (int a=1; a<10; a++){
        for (int b=1; b<10; b++){
            if(b!=a){
                for (int c=1; c<10; c++){
                    if(c!=a&&c!=b){
                        for(int d=1; d<10; d++){
                            if(d!=a&&d!=b&&d!=c){
                                for(int e=1; e<10; e++){
                                    if(e!=a&&e!=b&&e!=c&&e!=d){
                                        //判断是否满足 ab * cde = adb * ce
                                        if((a*10+b)*(c*100+d*10+e)==(a*100+d*10+b)*(c*10+e)){
                                            //为了验证结果是否正确, 可以将所有可能的结果都打印出来, 随机抽取几个手动验算
                                            printf("((%d*10+%d)*(%d*100+%d*10+%d)==(%d*100+%d*10+%d)*(%d*10+%d))=%d\n",a,b,c,d,e,a,d,b,c,e,(a*100+d*10+b)*(c*10+e));
                                            ans++;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    cout << ans << endl;
    return 0;
}

正确答案是:

142

2013 年蓝桥杯 C 语言 B 组省赛第 1 题 高斯日记

题目

题目标题: 高斯日记

大数学家高斯有个好习惯:无论如何都要记日记。

他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210

后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?

高斯出生于:1777年4月30日。

在高斯发现的一个重要定理的日记上标注着:5343,因此可算出那天是:1791年12月15日。

高斯获得博士学位的那天日记上标着:8113

请你算出高斯获得博士学位的年月日。

提交答案的格式是:yyyy-mm-dd, 例如:1980-03-21

请严格按照格式,通过浏览器提交答案。
注意:只提交这个日期,不要写其它附加内容,比如:说明性的文字。

题目分析

这一题是一个结果填空题,可以使用 Excel 解出答案。需要注意的有一下几点:

  • 充分利用和验证题目的示例,确定高斯出生的那一天是按第一天而不是第 0 天计算的。
  • 注意答案的提交格式,不要写成“1980-3-21”,而是:“1980-03-21”。
  • 注意闰年和非闰年的区别。能被 4 整除, 但不能被 100 整除的是闰年, 或者能被 400 整除的也是闰年. 闰年有 366 天,非闰年有 365 天. 闰年的 2 月有 29 天, 非闰年的 2 月有 28 天. 每 4 年中会有一个闰年.

首先, 借助 Excel 来计算出答案.

写下高斯的生日, 右键将单元格格式设置成”日期”:

图 1
图 1

根据年份和月份的规律可以计算出这样一个表格:

图 2

根据如下图所示的验证结果可以看到, 高斯出生的那天是按照第一天计算的, 同时这也验证了我们的计算方式是正确的:

图 3

当我们选中到 1978 年的时候,求和结果显示有 7916 天:

图 4
图 4

8113 – 7916 = 197

之后的 1799 年是非闰年, 其 2 月有28天, 计算如下:
31(1月) + 28(2月) + 31(3月) + 30(4月) + 31(5月) + 30(6月) = 181
197 – 181 = 16

因此可以知道, 高斯获取博士学位那一年是 1799 年 07 月 16 日
填写的答案就是:
1799-07-16

另外, 本题也可以使用编程解决, 程序如下:

//模仿翻日历的方式计算高斯获得博士学位的日期
#include<iostream>
using namespace std;

//判断是否是闰年的布尔函数
bool isLeapYear(int y){
        return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
    }

int main(int argc, const char *argv[]){
    int y = 1777;
    int m = 4;
    int d = 30;

    for (int i = 0; i < 8112; i++){
        d++;

        //如果出现 12 月 32 日 则代表下一年
        if (m == 12 && d == 32){
            y++;
            m = 1;
            d = 1;
            continue;
        }       
        //对除了 12 月之外的其他有 31 天的月份进行判断
        if ((m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10) && d == 32){
            m++;
            d = 1;
            continue;
        }

        //对除了 12 月之外的其他有 30 天的月份进行判断    
        if ((m == 4 || m == 6 || m == 9 || m == 11) && d == 31){
            m++;
            d = 1;
            continue;
        }       

        //判断闰年的 2 月
        if (m==2 && isLeapYear(y) && d == 30){
        //闰年的二月有 29 天, 因此如果出现第 30 天就需要进入下一月.
            m ++;
            d = 1;
            continue;
        }       

        //判断非闰年的 2 月
        if (m == 2 && !isLeapYear(y) && d == 29){
        //非闰年的二月有 28 天, 因此如果出现第 29 天就需要进入下一月.  
            m++;
            d = 1;
            continue;
        }
    }
    cout << y << " " << m << " " << d << endl;
    return 0;
}

运行结果:

图 5
图 5

需要注意的是, 填写答案时要按照要求的格式填写:
1799-07-16

CentOS/Ubuntu 创建 SWAP 交换分区

之前我写过一篇创建 Linux 交换分区的文章:
在不重装系统的情况下创建Linux的Swap分区
但是这次创建交换分区的方法和上次的有些区别, 再次记录一下.

操作环境

本文方案在以下操作系统中测试通过:
CentOS 7
Ubuntu 16.04

操作步骤

查看系统中的分区:

fdisk -l

或者, 查看当前分区:

df ./

根据上面查找到的分区信息, 选定一块分区, 从中划分出一个交换分区, 例如, 我选定的是 /dev/vda1 分区.

/dev/vda1 分区中分出一块大小为 4GB (4194304 字节) 的空间, 输出为 /var/swap, 命令如下:

dd if=/dev/vda1 of=/var/swap bs=1024 count=4194304

/var/swap 设置为交换分区:

mkswap /var/swap

设置开机自动挂载, 编辑:

vim /etc/fstab

添加如下信息:

/var/swap swap swap defaults 0 0

最后重启服务器:

reboot

重启后登录服务器就可以看到一个大小为 4GB 的交换分区了, 如图:

图 1
图 1

2017 年蓝桥杯 C 语言 B 组省赛第 1 题: 购物单

题目

标题: 购物单

小明刚刚找到工作,老板人很好,只是老板夫人很爱购物。老板忙的时候经常让小明帮忙到商场代为购物。小明很厌烦,但又不好推辞。

这不,XX大促销又来了!老板夫人开出了长长的购物单,都是有打折优惠的。
小明也有个怪癖,不到万不得已,从不刷卡,直接现金搞定。
现在小明很心烦,请你帮他计算一下,需要从取款机上取多少现金,才能搞定这次购物。

取款机只能提供100元面额的纸币。小明想尽可能少取些现金,够用就行了。
你的任务是计算出,小明最少需要取多少现金。

以下是让人头疼的购物单,为了保护隐私,物品名称被隐藏了。

-----------------
****     180.90       88折
****      10.25       65折
****      56.14        9折
****     104.65        9折
****     100.30       88折
****     297.15        半价
****      26.75       65折
****     130.62        半价
****     240.28       58折
****     270.62        8折
****     115.87       88折
****     247.34       95折
****      73.21        9折
****     101.00        半价
****      79.54        半价
****     278.44        7折
****     199.26        半价
****      12.97        9折
****     166.30       78折
****     125.50       58折
****      84.98        9折
****     113.35       68折
****     166.57        半价
****      42.56        9折
****      81.90       95折
****     131.78        8折
****     255.89       78折
****     109.17        9折
****     146.69       68折
****     139.33       65折
****     141.16       78折
****     154.74        8折
****      59.42        8折
****      85.44       68折
****     293.70       88折
****     261.79       65折
****      11.30       88折
****     268.27       58折
****     128.29       88折
****     251.03        8折
****     208.39       75折
****     128.88       75折
****      62.06        9折
****     225.87       75折
****      12.89       75折
****      34.28       75折
****      62.16       58折
****     129.12        半价
****     218.37        半价
****     289.69        8折
--------------------

需要说明的是,88折指的是按标价的88%计算,而8折是按80%计算,余者类推。
特别地,半价是按50%计算。

请提交小明要从取款机上提取的金额,单位是元。
答案是一个整数,类似4300的样子,结尾必然是00,不要填写任何多余的内容。

特别提醒:不许携带计算器入场,也不能打开手机。

题目分析

首先 ,这道题可以直接使用系统中的计算器逐个手算, 然后取一个大于且最接近整百的数值即可.
另外一种方法是使用 Word + Excel 辅助计算, 过程如下:

  1. 复制购物单的内容到 Word, 并使用替换功能去掉 *** 和”折”这些无关内容 (替换为空内容), 需要特别注意的是, 购物单中的”9折”, “8折”, 如果去掉”折”之后会变成”9″和”8″, 不符合实际意义, 应改成”90″和”80″;
  2. 把”半价”替换为”50″;
  3. 全选经过上面步骤处理后得到的文本, 依次打开”插入 / 表格 / 文本转换成表格”, 注意将”文字分隔位置”设置成”空格”, 这样生成的表格中, 上面文本中的单价和折扣额度会位于两列而不是一列, 如图 1:
图 1
图 1
  1. 删除生成的表格中空白的行和列;
  2. 新建一个 Excel 文件, 全选并复制 Word 文档中的表格, 之后粘贴到新建的 Excel 中;
  3. 在 Excel 的公式栏输入如下公式:
=A1*B1/100

之后拖动十字下拉柄将余下的列都进行上述计算;

  1. 输入如下公式求和:
=SUM(C1:C50)

计算结果如下:

5136.8595

因此应该填入的答案是:

5200

递归 (一): 递归思想与 C++ 中的递归函数及两个递归应用示例 (斐波那契, 汉诺塔)

什么是递归

从汇编层面上看递归

在汇编层面上, 递归可以看作是两个循环, 每个循环的循环参数都由另一个循环传入

从算法思想上看递归

递归是基于分治的, 也就是”分而治之”. 当需要求解的问题的输入规模为 N, 而 N 又很大的时候, 直接按照线性过程求解将十分困难. 这个时候, 如果 N 具有某些特性, 能够被分解成更小规模的问题 n, 并且这些小规模的问题 n 满足以下条件:

  • 由全部 n 的解能够得出 N 的解;
  • 全部 n 之间互相独立, n 与 n 之间不存在公共区域.
    这时, 我们就可以考虑使用”分支”来对 N 进行求解. 如果求解小规模问题 n 的方法和求解大规模问题 N 的方法是一致的 (n 具有 N 的最优子结构性质), 那么这时候就可以考虑更进一步地使用递归的思想求解.

从高级程序语言形式上看递归

在高级语言中, 递归的表现形式就是一个函数直接或间接地调用了自己, 直到满足出口条件, 结束递归.

从语义上看递归

“递归”分开就是”递”和”归”, “递”是离开自己, “归”是回到自己. 中文中的”递归”一词也表明了递归函数是一个来回往复的运算过程. 在英文中, “递归”的英文是”recursion”, “recursion”包含”重复”的意思. 另外, “递归”与”循环”在语义上也有明显的区别, “递归 (recursion)”是一个一维的往复结构, 而”循环 (loop)”则是一个二维的环状结构.

C++ 中的递归函数示例

斐波那契数列 (Fibonacci sequence)

斐波那契数列来源于斐波那契在其著作《计算之术》中提出的一个问题:

在第一个月有一对刚出生的小兔子,在第二个月小兔子变成大兔子并开始怀孕,第三个月大兔子会生下一对小兔子,并且以后每个月都会生下一对小兔子。 如果每对兔子都经历这样的出生、成熟、生育的过程,并且兔子永远不死,那么兔子的总数是如何变化的?

斐波那契《计算之术》

对于上面这个问题我们可以得出这样一个过程:

第 01 个月: 01 只小兔子 –> 01

第 02 个月: 01 只大兔子 –> 01

第 03 个月: 01 只大兔子 + 01 只小兔子 –> 02

第 04 个月: 02 只大兔子 + 01 只小兔子 –> 03

第 05 个月: 03 只大兔子 + 02 只小兔子 –> 05

第 06 个月: 05 只大兔子 + 03 只小兔子 –> 08

第 07 个月: 08 只大兔子 + 05 只小兔子 –> 13

于是, 我们得到了这样一个数列:

1,1,2,3,5,8,13,21,34…

这就是斐波那契数列 (也叫”兔子数列”).

我们先来借助 for 循环, 使用迭代来计算前 30 位斐波那契数列, 程序如下:

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define MAX_SIZE 30
int main(){
    int a[MAX_SIZE];
    a[0]=0;
    a[1]=1;
    printf("%d ",a[1]);

    for(int i=2;i<=MAX_SIZE;i++){
        a[i]=a[i-1]+a[i-2];
        printf("%d ",a[i]);
    }
    return 0;
} 

运行结果:

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229

迭代实现的求解斐波那契数列很好理解, 下面使用递归来实现一下.

下面的程序可以计算斐波那契数列中指定位置的值, 程序如下:

#include<stdio.h>
#include<bits/stdc++.h>

using namespace std;

int f(int n){
    if(n==0){
        return 1;
//斐波那契数列的首位是 1, 因此返回 1 
    }else if(n==1){
        return 1;
//斐波那契数列的第 2 位是 1, 因此返回 1 

    }else{
        return f(n-1)+f(n-2);
//从斐波那契数列的第 3 位及以后开始都可以使用前面两位的和计算出 
    }   
}

int main(){
    int a=f(7); //返回斐波那契数列的第 7 个值(从 0 开始计算) 
    printf("%d\n",a);
    system("pause");
    return 0;
} 

为了搞清楚递归的函数调用过程, 我们对上面的程序设置两个断点, 之后采用”单步进入”的方式逐步观察计算过程, 为了方便说明, 我们将函数 f() 的参数设置为 5, 即:

int a=f(5);

断点设置情况如下:

图 1
图 1

下面开始逐步分析展示本次调试过程:

01: a 为一个随机的初始值 31.

图 2
图 2

02: 参数 5 被传递给函数 f(n), n=5.

图 3
图 3

03: n 进入 else if 语句并和 1 对比判断是否相等, 此时 n=5.

图 4
图 4

04: 由于 5!=1, n 进入 else 语句, 此时 return f(5-1)+f(5-2);

图 5
图 5

05: 由于不知道上一步需要的 f(4) 和 f(3) 的值, 因此 n-1=4 并重新执行 f(n) 函数, 此时 n=4.

图 6

06: n 进入 else if 语句并和 1 对比判断是否相等, 此时 n=4.

07: 由于 4!=1, n 进入 else 语句, 此时 return f(4-1)+f(4-2);

08: 由于不知道上一步需要的 f(3) 和 f(2) 的值, 因此 n-1=3 并重新执行 f(n) 函数, 此时 n=3.

09: n 进入 else if 语句并和 1 对比判断是否相等, 此时 n=3.

10: 由于 3!=1, n 进入 else 语句, 此时 return f(3-1)+f(3-2);

11: 由于不知道上一步需要的 f(2) 和 f(1) 的值, 因此 n-1=2 并重新执行 f(n) 函数, 此时 n=2.

12: n 进入 else if 语句并和 1 对比判断是否相等, 此时 n=2.

13: 由于 2!=1, n 进入 else 语句, 此时 return f(2-1)+f(2-2);

14: 由于不知道上一步需要的 f(1) 和 f(0) 的值, 因此 n-1=1 并重新执行 f(n) 函数, 此时 n=1.

15: n 进入 else if 语句并和 1 对比判断是否相等, 此时 n=1, 由于 1=1 为真, 因此返回 1 并跳出 if 判断语句:

图 7
图 7

16: 经过上面的步骤知道了 f(1) 的值, 但是还不知道 f(0) 的值, 因此 n-1=1 并重新执行 f(n) 函数, 此时 n=0. 由于 0=0 为真, 因此返回 1, 至此, 边界条件 f(0) 和 f(1) 的值都知道了.

图 8
图 8

(后续过程不再使用文字描述, 见下面的图片说明.)

这个过程其实就是一个二叉树的遍历过程, 我用图片的形式绘制了整个过程, 如图 9:

图 9 斐波那契数列的递归求解过程也是对二叉树的遍历过程
图 9 斐波那契数列的递归求解过程也是对二叉树的遍历过程

上面这个程序运行一次只能输出斐波那契数列中的一个数值, 下面对该程序进行一个改进, 使得可以输出指定的前 n 位的斐波那契数列, 程序如下:

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;

int f(int n){
    if(n==0){
        return 1;
    }else if(n==1){
        return 1;
    }else{
        return f(n-1)+f(n-2);
    }   
}


int main(){
    int n=5; //设置打印前 6 个斐波那契数列元素 
    for(int i=0;i<=n;i++){
        int a=f(i);
        printf("%d ",a);
    }
    system("pause");
    return 0;
}

上述程序的运行结果是:

1 1 2 3 5 8

汉诺塔 (Tower of Hanoi)

Wikipedia 上对于汉诺塔问题的说明如下:

The Tower of Hanoi (also called the Tower of Brahma or Lucas’ Tower and sometimes pluralized) is a mathematical game or puzzle. It consists of three rods and a number of disks of different sizes, which can slide onto any rod. The puzzle starts with the disks in a neat stack in ascending order of size on one rod, the smallest at the top, thus making a conical shape.

The objective of the puzzle is to move the entire stack to another rod, obeying the following simple rules:
1. Only one disk can be moved at a time.
2. Each move consists of taking the upper disk from one of the stacks and placing it on top of another stack or on an empty rod.
3. No larger disk may be placed on top of a smaller disk.

With 3 disks, the puzzle can be solved in 7 moves. The minimal number of moves required to solve a Tower of Hanoi puzzle is 2n − 1, where n is the number of disks.

Wikipedia, Source: https://en.wikipedia.org/wiki/Tower_of_Hanoi

原始的汉诺塔问题是一个来自于印度的数学游戏, 在该游戏中, 有三根柱子, 其中一根柱子上有 64 个圆盘, 且这些圆盘都按照较大的圆盘在较小的圆盘下面的原则放置, 如图 10:

图 10 图片来自 Wikipedia, Source: https://commons.wikimedia.org/wiki/File:Tower_of_Hanoi.jpeg
图 10 图片来自 Wikipedia, Source: https://commons.wikimedia.org/wiki/File:Tower_of_Hanoi.jpeg

现在要把这些圆盘从目前的柱子上移动到另外两根柱子的其中一根上, 要求如下:

  • 一次只能移动一个圆盘.
  • 大圆盘必须始终在小圆盘的下面.

汉诺塔问题十分适合使用递归来解决, 使用”分治”的思想来思考这个问题就能找到解决问题的思路. 我们首先考虑如果这个汉诺塔问题只有两个圆盘时该如何移动圆盘(假设三个柱子从左向右依次为 A, B, C):

  • 把 A 上较小的圆盘放到 B 上
  • 把 A 上较大的圆盘放到 C 上
  • 把 B 上较小的圆盘放到 C 上

通过这三步, 将 B 柱作为一个中转柱, 我们就完成了把 A 上的圆盘全部转移到 C 上任务, 整个过程都符合汉诺塔的游戏规则.

但是, 我们现在有 64 个圆盘, 不是 2 个圆盘, 这该怎么办呢? 我们这时可以用”分治”的思想来想想看. 如图 11, 我们把 A 柱上较小的 63 个圆盘看作一个圆盘, 把下面最大的那个圆盘看作 1 个圆盘, 这样就成了”两个圆盘”, 因此可以使用上面关于只有两个圆盘时的移动方式来解决这个问题, 过程都显示在下面的图片里了:

图 11
图 11

那么之后该怎么做呢?

当我们把最大的一个圆盘移动到 C 上, 把 63 个较小的圆盘移动到 B 上之后, 即图 11 中的第 3 步, 我们可以发现, 此时圆盘的分布和图 11 中的第 2 步是很相似的, 只不过空出来的柱子由 C 变成了 A, 于是, 就有了下面这个过程:

  • 把 A 柱上的前 63 个圆盘移动到 B 柱 (此时 B 柱作为中转);
  • 把 A 柱上的第 64 个圆盘移动到 C 柱;
  • 把 B 柱上的前 62 个圆盘移动到 A 柱 (此时 A 柱作为中转);
  • 把 B 柱上的第 63 个圆盘移动到 C 柱;
  • 把 A 柱上的前 61 个圆盘移动到 B 柱 (此时 B 柱作为中转);;
  • 把 A 柱上的第 62 个圆盘移动到 C 柱;

将多个圆盘看作是一个圆盘与真的是一个圆盘其实是一样的. 下面我们从递归的程序角度思考一下这个过程:

  • 如果要移动第 64 个, 必须先移动完前 63 个;
  • 如果要移动第 63 个, 必须先移动完前 62 个;
  • 如果要移动第 62 个, 必须先移动完前 61 个;
  • (…省略若干行…)
  • 如果要移动第 2 个, 必须先移动完第 1 个;
  • 第 1 个是可以自由移动的, 于是, 我们把第 1 个移动到 B 柱(由于不是只有 1 个圆盘, 因此第 1 个圆盘要去中转柱而不是最终的目标柱);
  • 现在可以把第 2 个移动到 C 柱了;
  • 但是 A 柱上的圆盘并没有被移动完, 为了能移动完, 我们现在必须想办法让第 3 个圆盘到 C 柱(之后以此类推可以让第 4,5,6…64个圆盘到 C 柱);
  • 于是, 我们把第 1 个圆盘从 B 柱移动到 A 柱, 把第 2 个圆盘从 C 柱移动到 B 柱, 把 第 1 个圆盘从 A 柱移动到 B 柱, 把第 3 个圆盘从 A 柱移动到 C 柱.
  • 好啦, 现在第 3 个圆盘成功的到了 C 柱.
  • 不过还没完, 我们需要重复上面的过程, 直到把第 64 个圆盘移动到 C 柱, 之后, 第 64 个圆盘在之后的移动过程中就可以不用动了, 一直待在 C 柱上.
  • 对于剩下的 63 个圆盘重复和原来 64 个圆盘一样的移动过程即可.

这么移动的工作量是很大的, 移动完全部 64 个圆盘需要大约 1800 亿亿步:

18,446,744,073,709,551,615

根据上面的介绍, 用 C++ 实现的话, 程序是这样的:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

//假设函数 (int n, char a, char b, char c) 的作用是将 n 个圆盘从 a 移动到 c 

void f(int n, char a, char b, char c){
        if(n==1){
            cout<<"将盘子"<<n<<"从"<<a<<"移动到"<<c<<endl;
    //当只有 1 个盘子时, 直接从 a 移动到 c 
        }else{
            f(n-1,a,c,b);
    //将 n-1 个圆盘由 a 移动到 b 
            cout<<"将盘子"<<n<<"从"<<a<<"移动到"<<c<<endl;
            f(n-1,b,a,c);
    //将 n-1 个圆盘由 b 移动到 c 
        }
    }

int main(){
    f(3,'a','b','c');                                                                                                                                                                                                                                                                                                    
    return 0;
}

上面这个程序的运行结果如下 (圆盘的序号越大则圆盘越大):

假设有三个圆盘, 移动顺序是这样的:

  • 将盘子1从a移动到c
  • 将盘子2从a移动到b
  • 将盘子1从c移动到b
  • 将盘子3从a移动到c
  • 将盘子1从b移动到a
  • 将盘子2从b移动到c
  • 将盘子1从a移动到c

假设有四个圆盘, 移动顺序是这样的:

  • 将盘子1从a移动到b
  • 将盘子2从a移动到c
  • 将盘子1从b移动到c
  • 将盘子3从a移动到b
  • 将盘子1从c移动到a
  • 将盘子2从c移动到b
  • 将盘子1从a移动到b
  • 将盘子4从a移动到c
  • 将盘子1从b移动到c
  • 将盘子2从b移动到a
  • 将盘子1从c移动到a
  • 将盘子3从b移动到c
  • 将盘子1从a移动到b
  • 将盘子1从a移动到b
  • 将盘子2从a移动到c
  • 将盘子1从b移动到c

最后, 来看几个关于汉诺塔问题的动图吧.

图 12:

图 12 By André Karwath aka Aka - Own work, CC BY-SA 2.5, https://commons.wikimedia.org/w/index.php?curid=85401
图 12 By André Karwath aka Aka – Own work, CC BY-SA 2.5, https://commons.wikimedia.org/w/index.php?curid=85401

图 12 By André Karwath aka Aka – Own work, CC BY-SA 2.5, https://commons.wikimedia.org/w/index.php?curid=85401

图 13:

图 13 By Trixx - I designed this using http://thewalnut.io/, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=43282866
图 13 By Trixx – I designed this using http://thewalnut.io/, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=43282866

CentOS Linux 下部署 phpMyAdmin 并解决报错

操作环境

操作系统: CentOS 7
PHP 版本: PHP 7
phpMyAdmin 版本: phpMyAdmin-4.8.5-all-languages
Web 服务器: Apache

phpMyAdmin 是一个基于 PHP 环境的运行在 Web 服务器上的 MySQL 数据库管理工具, 借助 phpMyAdmin, 可以在 Web 浏览器上管理 Mysql 数据库.

phpMyAdmin 官网地址: https://www.phpmyadmin.net/

操作步骤

phpMyAdmin 需要借助 Web 服务器运行, 因此首先进入 Web 服务器的网站根目录:

cd /var/www/html/

下载 phpMyAdmin 安装文件:

wget https://files.phpmyadmin.net/phpMyAdmin/4.8.5/phpMyAdmin-4.8.5-all-languages.zip

解压缩:

unzip phpMyAdmin-4.8.5-all-languages.zip

为了尽可能防止 phpMyAdmin 被恶意破解, 可以把解压得到的 phpMyAdmin 默认的文件名改成一个随机的名称:

mv phpMyAdmin-4.8.5-all-languages sofdfdlrbvswskdscdf

之后复制 phpMyAdmin 提供的示例配置文件为我们自己的配置文件:

cd sofdfdlrbvswskdscdf
cp -p config.sample.inc.php config.inc.php

之后可以在浏览器中使用 IP/sofdfdlrbvswskdscdf 或者 域名/sofdfdlrbvswskdscdf 打开 phpMyAdmin 的登陆界面并使用 MySQL 的用户名和密码登陆.

解决报错

报错 1: The configuration file now needs a secret passphrase (blowfish_secret).

这个报错直接翻译过来就是:”配置文件现在需要一个机密口令 (blowfish_secret)”.
出现这个报错是因为 phpMyAdmin 使用了 blowfish 加密算法对 CCOKIE 进行认证, 因此用户必须给定一个随机字符串用于认证.

解决步骤:
在 phpMyAdmin 的根目录下打开配置文件:

vim config.inc.php

可以看到:

$cfg['blowfish_secret'] = ''; /* YOU MUST FILL IN THIS FOR COOKIE AUTH! */

我们填入一些随机字符, 可以包含大小写字母, 数字和特殊字符, 例如:

$cfg['blowfish_secret'] = 'h2s#AjfF%f*AdSk$w';

这些随机字符串要足够长, 至少需要 32 位, 否则会报如下错误(上面示例中的随机字符串不够长):

The secret passphrase in configuration (blowfish_secret) is too short.

完成上述操作并保存退出之后, 重新登陆 phpMyAdmin 即可发现该报错已消失.

报错 2: The $cfg‘TempDir’ is not accessible. phpMyAdmin is not able to cache templates and will be slow because of this.

这个报错翻译过来就是:”无法获取 $cfg‘TempDir’. phpMyAdmin 无法缓存模板并将因此变慢”.

解决步骤:
在 phpMyAdmin 的根目录下创建一个 tmp 文件并修改其所属用户和组即可, 命令:

mkdir tmp
chown -R apache:root tmp/

之后, 刷新 phpMyAdmin 的 Web 界面, 该报错即消失.

本文所示的方法同样可以用于在 Ubuntu Linux 等其他大部分 Linux 发行版中部署 phpMyAdmin.

基于 Ubuntu Linux 和 OwnCloud 部署私有云存储

操作环境

Ubuntu 版本: Ubuntu 18.04.1 LTS

OwnCloud 版本: 10.0.10

OwnCloud建议的内存只需要512M, 为了保证系统的可靠运行, 建议使用系统内存为 1GB 及以上的服务器.

10.0.10版的OwnCloud的下载地址如下: https://download.owncloud.org/community/owncloud-10.0.10.tar.bz2

当前的最新版下载地址,可以在OwnCloud的官网下载页面找到: https://owncloud.org/download/

操作步骤

部署 Apache2

sudo apt-get install -y apache2

部署 mariadb

sudo apt-get install -y mariadb-server

新安装的 mysql 数据库的 root 用户的密码默认为空,我们可以使用如下命令为数据库root用户设置密码:

sudo mysqladmin -u root password 密码

设置好之后,登录mysql:

sudo mysql -u root -p

登陆之后,创建一个数据库owncloud:

MariaDB [(none)]> create database owncloud;

创建一个数据库用户 owncloud 并设置密码和权限:

MariaDB [(none)]> grant all on owncloud.* to 'owncloud'@'localhost' identified by '密码';
MariaDB [(none)]> flush privileges;

“flush privileges”用于更新权限.

部署 PHP 7

sudo apt-get install -y php7.0

PHP 7 安装完成后,使用如下命令验证一下 PHP 环境是否配置成功:

php -v

回显如下:

PHP 7.2.10-0ubuntu0.18.04.1 (cli) (built: Sep 13 2018 13:45:02) ( NTS )<br>Copyright (c) 1997-2018 The PHP Group<br>Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies<br>•    with Zend OPcache v7.2.10-0ubuntu0.18.04.1, Copyright (c) 1999-2018, by Zend Technologies

这说明 PHP 环境已经配置成功了。

为了让 Apache2 能够识别 PHP 文件(如果 Apache2 不能识别 PHP 文件,那么当我们通过浏览器访问一个 .php 格式的文件时,浏览器会下载该文件而不是显示该文件) 首先搜索一下有没有相关插件:

apt-cache search libapache2-mod-php

回显如下:

libapache2-mod-php - server-side, HTML-embedded scripting language (Apache 2 module) (default)
libapache2-mod-php7.2 - server-side, HTML-embedded scripting language (Apache 2 module)
php7.2-fpm - server-side, HTML-embedded scripting language (FPM-CGI binary)

根据提示,安装 libapache2-mod-php7.2 这个插件:

sudo apt-get install libapache2-mod-php7.2

为了验证上述插件安装及运行正确,我们可以进行一个测试。

进入 Apache2 服务器的根目录:

cd /var/www/html/

新建一个 .php 文件:

sudo vim 1.php

写入如下内容:

<?php
    phpinfo();
?>

确保 Apache2 服务器正在运行:

systemctl status apache2

之后,就在浏览器中访问该 PHP 文件,看到如下界面就证明 Apache2 服务器可以正常识别并解析 PHP 格式的文件:

图 1
图 1

警告:验证完毕后务必删除该文件,否则将可能泄露系统信息。

安装 PHP 的 zip 模块:

sudo apt-get install -y php7.2-zip

安装 PHP 的 intl 模块:

sudo apt-get install -y php7.2-intl

安装 PHP 的 XML 模块:

sudo apt-get install -y php7.2-xml

安装 PHP 的 cURL 模块:

sudo apt-get install -y php7.2-curl

安装 PHP 的 GD 模块:

sudo apt-get install -y php7.2-gd

安装 PHP 的 mbstring 模块:

sudo apt-get install -y php7.2-mbstring

安装 PHP 的 MySQL 模块:

sudo apt-get install -y php7.2-mysql

部署OwnCloud

下载OwnCloud服务器端安装文件:

 wget https://download.owncloud.org/community/owncloud-10.0.10.tar.bz2

下载对应的hash256验证文件:

wget https://download.owncloud.org/community/owncloud-10.0.10.tar.bz2.sha256

验证安装文件是否完整

将上面下载下来的两个文件放在同一路径下,验证安装文件是否完整:

sha256sum -c owncloud-10.0.10.tar.bz2.sha256 <owncloud-10.0.10.tar.bz2

如果文件没有问题,则会返回OK:

owncloud-10.0.10.tar.bz2: OK

解压安装包, 命令:

 tar -xjf owncloud-10.0.10.tar.bz2

成功解压后会得到一个名为owncloud的文件夹。

复制安装文件到指定目录:

sudo cp -r owncloud /var/www/html

在Apache服务器目录下创建OwnCloud的配置文件:

cd /etc/apache2/sites-available/
sudo touch owncloud.conf
sudo vim owncloud.conf

写入如下配置:

Alias /owncloud "/var/www/html/owncloud/"

<Directory /var/www/html/owncloud/>
  Options +FollowSymlinks
  AllowOverride All

 <IfModule mod_dav.c>
  Dav off
 </IfModule>

 SetEnv HOME /var/www/html/owncloud
 SetEnv HTTP_HOME /var/www/html/owncloud

</Directory>

接下来创建一个symlink:

sudo ln -s /etc/apache2/sites-available/owncloud.conf /etc/apache2/sites-enabled/owncloud.conf

更改 owncloud 目录所属的用户和用户组:

sudo chown -R www-data:www-data /var/www/html/owncloud/

重启Apache:

sudo systemctl restart apache2

输入Your Server IP/owncloud/ 就看到OwnCloud的安装界面了:

图 2

图 2

创建管理员的用户名和密码:

图 3
图 3

配置存储路径和数据库:

图 4
图 4

安装完成后就可以使用管理员账户登录了:

图 5
图 5

直接使用管理员账户登录是有风险的,我们可以创建一个非管理员账户,加入非Admin组。创建用户时输入用户名,用户邮箱和用户所有的组即可以创建一个用户:

图 6
图 6

创建完成后的用户是没有设置自定义密码的,在该用户对应的“密码”一栏可以更改用户密码:

图 7
图 7

输入新密码后回车即可以看到密码修改成功的提示:

图 8
图 8

至此, 私有云存储服务器端的基础配置基本完成.

参考文献

OwnCloud官网提供的安装手册: https://doc.owncloud.org/server/latest/admin_manual/installation/

2015 年蓝桥杯 C 语言 B 组省赛第 2 题: 星系炸弹

题目

星系炸弹

在X星系的广袤空间中漂浮着许多X星人造“炸弹”,用来作为宇宙中的路标。 每个炸弹都可以设定多少天之后爆炸。 比如:阿尔法炸弹2015年1月1日放置,定时为15天,则它在2015年1月16日爆炸。 有一个贝塔炸弹,2014年11月9日放置,定时为1000天,请你计算它爆炸的准确日期。

请填写该日期,格式为 yyyy-mm-dd 即4位年份2位月份2位日期。比如:2015-02-19 请严格按照格式书写。不能出现其它文字或符号。

题目分析

本题用 Excel 或者用程序计算都可以 (用 Excel 计算的方法本文不做过多介绍).

这里需要注意的就是闰年和非闰年以及大月小月和 2 月, 闰年的 2 月有 29 天, 非闰年的 2 月有 28 天, 因此, 闰年有 366 天, 非闰年有 365 天.

另外, 根据示例, 放置炸弹的那天按第 0 天计算.

本题正确答案是:

2017-08-05

程序:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int sum=1000;
	int Mdays[12]={31,28,31,30,31,30,31,31,30,31,30,31};
	int Y=2014;
	int M=11;
	int D=9;
	
//2014 年 11 月 09 日距离爆炸有 1000 天, 在此循环 1000 次
	for(int i=1;i<=sum;i++){ 
		D++; //每循环一次则日数加 1 
		if(D>Mdays[M-1]){ //如果日数大于 11 月的日数, 则日数重置为 1, 月数加 1 
			D=1;
			M++;
			if(M>12){ //如果月数大于 12, 则月数重置为 1, 年数加 1. 
				M=1;
				Y++;
				
				//新开始一年之后, 对是否为闰年进行判断 
				if((Y%400==0)||(Y%4==0&&Y%100!=0)){
					Mdays[1]=29;
				}else{
					Mdays[1]=28;
				}
			}
		} 
	} 
	cout<<Y<<"-"<<M<<"-"<<D<<endl;
	return 0;
}

2015 年蓝桥杯 C 语言 B 组省赛第 1 题: 奖券数目 (四种解法 + 详细分析)

题目

奖券数目

有些人很迷信数字,比如带“4”的数字,认为和“死”谐音,就觉得不吉利。 虽然这些说法纯属无稽之谈,但有时还要迎合大众的需求。某抽奖活动的奖券号码是5位数(10000-99999),要求其中不要出现带“4”的号码,主办单位请你计算一下,如果任何两张奖券不重号,最多可发出奖券多少张。

请提交该数字(一个整数),不要写任何多余的内容或说明性文字。

题目分析

本题的主要解题思路就是枚举 10000-99999 之间的所有数字, 然后判断其中是否含有 4 , 如果不含有 4 则计数器加 1.

在具体实现上, 有四种解法. 第一种解法是对每个枚举结果都进行分解, 将原来的 5 位数分解成 5 个 1 位数, 之后逐个数字判断是否含有数字 4; 第二种方法是把每个枚举的结果都转换成字符串, 之后判断这个字符串中是否包含字符”4″, 如果不包含则计数器加 1; 第三种解法是使用 5 个 for 循环, 模拟奖券的五位数, 之后进行枚举和判断; 第四种解法是使用数学方法求解. 由于除了数字 4 之外, 一共有 9 个数字可以使用, 而且最高位不能为 0, 那么可以计算出符合条件的个数为:

8*9*9*9*9=52488

本题的正确答案是:

52488

下面针对上述分析, 分别求解如下.

方法一

将 10000-99999 之间的所有 5 位数都逐个分解成 5 个 1 位数, 之后逐个数字判断是否为数字 4.

程序:

#include<iostream>
using namespace std;
int main(){
	int ans = 0;
	for(int i=10000;i<=99999;i++){
		
		//将每一位上的数字都分离出来
		//(i%1000) 取余将去掉当前最高位后形成新的数字
		//(i%10000)-(i%1000) 将把当前最高位之后的数字都变成 0
		//((i%10000)-(i%1000))/1000 将去掉最高位后面的 0, 形成一个个位数 
		int a = (i-(i%10000))/10000;	
		int b = ((i%10000)-(i%1000))/1000;
		int c = ((i%1000)-(i%100))/100;
		int d = ((i%100)-(i%10))/10;
		int e = (i%10);
		
		//如果每个位上的数字都不是 4 则计数器加 1 
		if(a!=4&&b!=4&&c!=4&&d!=4&&e!=4){
			ans++;
		}		
	}
	cout<<ans<<endl;
	return 0;
}

方法二

把 10000-99999 之间的每个枚举的结果都转换成字符串, 之后判断这个字符串中是否包含字符”4″.

程序:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
void i2s(int num, string &str){
//stringstream 类的作用是将数字转换成字符串
		stringstream ss;

//将需要转换的数字 sum 使用 << 运算符传递给 ss 对象 
		ss << num;
		
//将转换后的字符串使用 >> 运算符传递给 str 变量 
		ss >> str;
	}
int main(){
	int ans=0;
	for(int i=10000;i<=99999;i++){
		string s;
		i2s(i,s);
		
//查找字符串 s 中是否不包含(注意: 不包含用的是 ==)'4'这个字符串 
		if(s.find('4')==string::npos){
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}

方法三

使用 5 个 for 循环, 模拟奖券的五位数, 之后进行枚举和判断. 程序:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int ans=0;
	for(int a=1;a<=9;a++){
		if(a!=4){
			for(int b=0;b<=9;b++){
				if(b!=4){
					for(int c=0;c<=9;c++){
						if(c!=4){
							for(int d=0;d<=9;d++){
								if(d!=4){
									for(int e=0;e<=9;e++){
										if(e!=4){
											ans++;
										}
									}
								}
							}
						}
					}
				}
			}
		}

	}
	cout<<ans<<endl;
	return 0;
}

方法四

由于除了数字 4 之外, 一共有 9 个数字可以使用, 而且最高位不能为 0, 那么可以计算出符合条件的奖券个数为:

8*9*9*9*9=52488

2015 年蓝桥杯 C 语言 B 组省赛第 4 题: 格子中输出 (详细分析)

题目

格子中输出

StringInGrid函数会在一个指定大小的格子中打印指定的字符串。 要求字符串在水平、垂直两个方向上都居中。 如果字符串太长,就截断。 如果不能恰好居中,可以稍稍偏左或者偏上一点。

下面的程序实现这个逻辑,请填写划线部分缺少的代码。

#include <stdio.h>
#include <string.h>

void StringInGrid(int width, int height, const char* s)
{
	int i,k;
	char buf[1000];
	strcpy(buf, s);
	if(strlen(s)>width-2) buf[width-2]=0;
	

```
printf("+");
for(i=0;i<width-2;i++) printf("-");
printf("+\n");

for(k=1; k<(height-1)/2;k++){
	printf("|");
	for(i=0;i<width-2;i++) printf(" ");
	printf("|\n");
}

printf("|");

printf("%*s%s%*s",_____________________________________________);  //填空
          
printf("|\n");

for(k=(height-1)/2+1; k<height-1; k++){
	printf("|");
	for(i=0;i<width-2;i++) printf(" ");
	printf("|\n");
}	

printf("+");
for(i=0;i<width-2;i++) printf("-");
printf("+\n");	
```

}

int main()
{
	StringInGrid(20,6,"abcd1234");
	return 0;
}

对于题目中数据,应该输出:

+------------------+
|                  |
|     abcd1234     |
|                  |
|                  |
+------------------+

(如果出现对齐问题,参看【图1.jpg】)

注意:只填写缺少的内容,不要书写任何题面已有代码或说明性文字。

图 1
图 1

题目分析

对于代码填空题, 我们不需要读完全部代码, 只需要知道需要填空的地方完成的是什么样的功能即可.

首先, 我们可以先把需要填空的地方注释掉, 看看没有这一句代码的话程序的执行结果是怎样的(如果缺少该句代码之后程序可以执行的话). 在本题中注释掉需要填空的代码之后, 运行结果如图 2 所示:

图 2

根据显示的结果可以看出, 缺少的代码就是用来打印格子中的字符串和用于对齐的空格的.

本题考查的是 C++ 中的 printf() 函数的 * , 在 C++ 的官网上可以查找到相关的 API. 在 C++ 官网搜索 printf 即可打开关于 printf() 函数的 API 说明文档 (蓝桥杯比赛的计算机上也提供有 C/C++ 的 API 说明文档), 地址如下:

http://www.cplusplus.com/reference/cstdio/printf/?kw=printf

从官网上我们可以找到这样一个说明, 如图 3:

图 3
图 3

在其下的示例中我们可以看到对 * 用法的演示 (如图 4):

图 4
图 4

题目中用到的是 %*s , 这里用到的是 %*d, 根据之前学习到的 printf() 函数中 %d 表示整型变量, %s 表示字符型变量的知识可以推测, %*d%*s 也是分别对应整型变量和字符型变量的. 为了验证, 我们可以写这样一段程序:

#include<stdio.h>
int main(){
	printf("%*d",3, 9);
	printf("\n"); 
	printf("%*s",6, "aa");
	return 0;
} 

运行结果如下:

  9
    aa

从输出结果可以发现, 第一行打印出了 2 个空格和 1 个数字 (2+1=3), 第二行打印出了 4 个空格和 2 个字符 (4+2=6).

由此可以看到, printf() 函数中的 * 是用来确定输出的”宽度”的, 需要向其传送两个变量, 一个是宽度值, 一个是要打印的变量, 当要打印的变量的长度小于宽度值时就用空格填充.

再来看需要填空的那行代码:

printf("%*s%s%*s",_____________________________________________);

关键之处是 %*s%s%*s . 根据上面的分析我们知道, 其中间的 %s 是用来打印字符串的, 两边的 %*s 是用来打印空格的. 由于需要对齐, 所以需要知道每行的总长度和字符串的长度才可以计算出要打印的空格的个数.

根据题目中的下面这些代码可以知道每行的长度为 width:

printf("+");
for(i=0;i<width-2;i++) printf("-");
printf("+\n");

由下面这行代码可以确定字符串变量 s 被复制给了变量 buf :

strcpy(buf, s);

strcpy() 函数的功能为:

复制字符串from 中的字符到字符串to,包括空值结束符。返回值为指针to。

来自 C/C++ API 文档

由下面这行代码可以知道字符串变量 s 的长度为 strlen(s) :

if(strlen(s)>width-2) buf[width-2]=0;

由于题目中有如下的说明:

如果不能恰好居中,可以稍稍偏左或者偏上一点。

因此, 当行长度为 20, 字符长度为 8 的时候, 左边的空格应该有 (20-8-2)/2=5 个, 右边的空格应该有 (20-8-2)/2=(20-8-1)/2=5 个. 如果行长度为 21, 字符长度为 8 的时候, 左边的空格应该有 (21-8-2)/2=5 个, 右边的空格应该有 (21-8-1)/2=6 个

由此可以确定, 字符串变量 s 的左边需要打印的空格数为:

(width-strlen(s)-2)/2

右边应该打印的空格数为:

(width-strlen(s)-1)/2

因此空格中应该填写的内容是:

width-2-strlen(s))/2," ",buf,(width-strlen(s)-1)/2," "

完整的程序代码如下:

#include <stdio.h>
#include <string.h>

void StringInGrid(int width, int height, const char* s)
{
	int i,k;
	char buf[1000];
	strcpy(buf, s);
	if(strlen(s)>width-2) buf[width-2]=0;
	

printf("+");
for(i=0;i<width-2;i++) printf("-");
printf("+\n");

for(k=1; k<(height-1)/2;k++){
	printf("|");
	for(i=0;i<width-2;i++) printf(" ");
	printf("|\n");
}

printf("|");

printf("%*s%s%*s",(width-2-strlen(s))/2," ",buf,(width-strlen(s)-1)/2," ");  //填空
          
printf("|\n");

for(k=(height-1)/2+1; k<height-1; k++){
	printf("|");
	for(i=0;i<width-2;i++) printf(" ");
	printf("|\n");
}	

printf("+");
for(i=0;i<width-2;i++) printf("-");
printf("+\n");	

}

int main()
{
	StringInGrid(20,6,"abcd1234");
	return 0;
}

2015 年蓝桥杯 C 语言 B 组省赛第 3 题: 三羊献瑞 (三种方法 + 详细分析)

题目

三羊献瑞

观察下面的加法算式:

      祥 瑞 生 辉
  +   三 羊 献 瑞
-------------------
   三 羊 生 瑞 气

(如果有对齐问题,可以参看【图1.jpg】)

其中,相同的汉字代表相同的数字,不同的汉字代表不同的数字。

请你填写“三羊献瑞”所代表的4位数字(答案唯一),不要填写任何多余内容。

图 1
图 1

题目分析

将这八个汉字分别用字母 a~h 和数组 aa[0]~aa[7] 对应表示如下:

三 -> a -> aa[0]

羊 -> b -> aa[1]

献 -> c -> aa[2]

瑞 -> d -> aa[3]

祥 -> e -> aa[4]

生 -> f -> aa[5]

辉 -> g -> aa[6]

气 -> h -> aa[7]

换算成题目中的形式分别是:

      e d f g
  +   a b c d
-------------------
    a b f d h

      aa[4] aa[3] aa[5] aa[6]
  +   aa[0] aa[1] aa[2] aa[3]
-------------------------------
aa[0] aa[1] aa[5] aa[3] aa[7]


这里之所以要分别使用字母和数组表示一个汉字是因为下面提到的三种方法要用到.

本题的要求可以总结如下:

  • 每个汉字对应的数字都是不同的.
  • a~h 这 8 个字母 (或者说 8 个数组元素) 的取值范围是从 0 到 9 十个数字.
  • 其中 a (aa[0]) 不能等于 0, e (aa[4]) 也不能等于 0.
  • 要求填入的结果是”三羊献瑞”所代表的四位数字, 而不是一共有多少种组合方式 (根据题意可知满足题目条件的组合方式只有一种).

解法一

这个解法基于 C++ 标准函数库 STL 中的 next_permutation() 全排列函数进行. 对 0~9 十个数字进行全排列, 取每次排列的前 8 个代入公式进行计算并检验是否满足要求. 由于进行的是全排列, 所以数组 aa[] 的前 8 个元素都有机会包含 0~9 这 10 个数字.

代码如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int aa[10]={0,1,2,3,4,5,6,7,8,9};
	int sum=0;
	int sum1=0;
	int sum2=0; 
	int ans=0;
	while(next_permutation(aa,aa+10)){
		if(aa[0]!=0&&aa[4]!=0){
			sum1=aa[4]*1000+aa[3]*100+aa[5]*10+aa[6];
			sum2=aa[0]*1000+aa[1]*100+aa[2]*10+aa[3];
			sum=aa[0]*10000+aa[1]*1000+aa[5]*100+aa[3]*10+aa[7];
			if(sum==(sum1+sum2)){
			break;
			}
		}
		
		
	}
	cout<<"祥瑞生辉:"<<endl; 
	cout<<" "<<sum1<<endl;
	
	cout<<"三羊献瑞:"<<endl; 
	cout<<" "<<sum2<<endl;
	
	cout<<"三羊生瑞气:"<<endl;
	cout<<sum<<endl;
	return 0;
}

正确答案:

1085

解法二

这个解法的思路和解法一类似, 只不过不使用 STL 提供的 next_permutation() 全排列函数, 而改用 8 个 for 循环进行暴力破解.

代码如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int sum, sum1, sum2;
	for(int a=1;a<=9;a++){ //a 不能等于 0 
		for(int b=0;b<=9;b++){
			if(b!=a){
				for(int c=0;c<=9;c++){
					if(c!=a&&c!=b){
						for(int d=0;d<=9;d++){
							if(d!=a&&d!=b&&d!=c){
								for(int e=1;e<=9;e++){ //e 不能等于 0 
									if(e!=a&&e!=b&&e!=c&&e!=d){
										for(int f=0;f<=9;f++){
											if(f!=a&&f!=b&&f!=c&&f!=d&&f!=e){
												for(int g=0;g<=9;g++){
													if(g!=a&&g!=b&&g!=c&&g!=d&&g!=e&&g!=f){
														for(int h=0;h<=9;h++){
															if(h!=a&&h!=b&&h!=c&&h!=d&&h!=e&&h!=f&&h!=g){
																sum1=e*1000+d*100+f*10+g;
																sum2=a*1000+b*100+c*10+d;
																sum=a*10000+b*1000+f*100+d*10+h;
																if(sum==(sum1+sum2)){
																	printf("%d\n",sum2);
																}
															}
															
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	}
	return 0;
}

正确答案:

1085


为了进一步验证结果的正确性, 也可以将 8 个汉字对应的结果都输出数来. 由于解法一中已经这么做, 这里就不再加入输出全部计算结果的代码. 输出全部计算结果后需要注意的是, 看清题意和计算结果, 不要误提交不符合题目要求的答案

解法三

这个解法用到了一些数学原理, 可以减少 for 循环的个数, 能够提高计算效率. 虽然对于填空题而言没必要考虑计算效率的问题, 但是这也是一种计算方式, 有必要尝试一下.

首先, 我们看一下这个式子:

      e d f g
  +   a b c d
-------------------
    a b f d h

两个四位数相加得出了一个五位数, 根据加法中”满十进一”的法则, 五位数的最高位一定是 1, 即:

a=1

于是, 原式就变成了:

      e d f g
  +   1 b c d
-------------------
    1 b f d h

这样我们就可以去掉对 a 的 for 循环, 对解法二中的代码进行一下修改即可.

代码如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int sum, sum1, sum2;
	int a=1;
		for(int b=0;b<=9;b++){
			if(b!=a){
				for(int c=0;c<=9;c++){
					if(c!=a&&c!=b){
						for(int d=0;d<=9;d++){
							if(d!=a&&d!=b&&d!=c){
								for(int e=1;e<=9;e++){ //e 不能等于 0 
									if(e!=a&&e!=b&&e!=c&&e!=d){
										for(int f=0;f<=9;f++){
											if(f!=a&&f!=b&&f!=c&&f!=d&&f!=e){
												for(int g=0;g<=9;g++){
													if(g!=a&&g!=b&&g!=c&&g!=d&&g!=e&&g!=f){
														for(int h=0;h<=9;h++){
															if(h!=a&&h!=b&&h!=c&&h!=d&&h!=e&&h!=f&&h!=g){
																sum1=e*1000+d*100+f*10+g;
																sum2=a*1000+b*100+c*10+d;
																sum=a*10000+b*1000+f*100+d*10+h;
																if(sum==(sum1+sum2)){
																	printf("%d\n",sum2);
																}
															}
															
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
	return 0;
}

一段错误的代码

下面这段代码是错误的:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
	int aa[10]={0,1,2,3,4,5,6,7,8,9};
	int sum=0;
	int sum1=0;
	int sum2=0; 
	int ans=0;
	while(next_permutation(aa,aa+10)){
		if(aa[0]!=0&&aa[4]!=0){
			sum1=aa[4]*1000+aa[3]*100+aa[5]*10+aa[6];
			sum2=aa[0]*1000+aa[1]*100+aa[2]*10+aa[3];
			sum=aa[0]*10000+aa[1]*1000+aa[5]*100+aa[3]*10;
			if(sum==(sum1+sum2)){
			break;
			}
		}	
	}
	cout<<sum1<<endl;
	cout<<sum2<<endl;
	cout<<sum<<endl;
	return 0;
}

运行结果是:

9347
1083
10430

由运行结果可以看到, 最后计算的和为”10430″, 出现了重复的 “0”, 而在题目中的结果里 “三 羊 生 瑞 气” 并没有重复的汉字, 因此这个结果是错误的, 原因在下面这段代码:

sum=aa[0]*10000+aa[1]*1000+aa[5]*100+aa[3]*10;

上面这段代码中没有加入个位, 因此导致错误.

在做题时要注意以下三点:

  • 认真读题, 充分理解题意后再做题.
  • 认真写代码, 长代码能分行写的就分开写, 不要出现不符合题意的代码.
  • 计算出结果后如果能手动验算, 一定要手动验算一遍.

荒原之梦网全部内容均为原创,提供了涵盖考研数学基础知识、考研数学真题、考研数学练习题和计算机科学等方面,大量精心研发的学习资源。

豫 ICP 备 17023611 号-1 | 公网安备 - 荒原之梦 豫公网安备 41142502000132 号 | SiteMap
Copyright © 2017-2024 ZhaoKaifeng.com 版权所有 All Rights Reserved.

Copyright © 2024   zhaokaifeng.com   All Rights Reserved.
豫ICP备17023611号-1
 豫公网安备41142502000132号

荒原之梦 自豪地采用WordPress