第十届“蓝桥杯”大赛软件类校选

第一题

问题描述

给你一个排好序的数组,请基于当前数组去除所有重复的元素。

输入

输入包含多组数据。对于每组数据: 第一行是n,表示数组有n个元素,当n=-1,表示输入结束;第二行是n个排序好的整数

输出

针对每组输入,输出去重后的数组

样例输入

5
1 2 2 3 3
-1

样例输出

1 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostream>
using namespace std;
const int maxn = 1000000;
int ans[maxn];
int main()
{
int n;
int tmp;
cin >> n;
while(n!=-1){
cin >> ans[0];
int k = 1; //数组ans的index
for(int i = 1;i < n;i++){
cin >> tmp;
if(tmp>ans[k-1]){
ans[k++] = tmp;
}
}
for(int j = 0; j < k;j++)
{
cout<<ans[j]<< " ";
}
cout << endl;
cin >> n;
}
return 0;
}

第二题

问题描述

四平方和定理,又称为拉格朗日定理: 每个正整数都可以表示为至多4个正整数的平方和。 如果把0包括进去,就正好可以表示为4个数的平方和。 比如:
5 = 0^2 + 0^2 + 1^2 + 2^2
7 = 1^2 + 1^2 + 1^2 + 2^2
(^符号表示乘方的意思) 对于一个给定的正整数,可能存在多种平方和的表示法。 要求你对4个数排序:0 <= a <= b <= c <= d,并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法
程序输入为一个正整数N (N<5000000) 要求输出4个非负整数,按从小到大排序,中间用空格分开

样例输入1

5

样例输出1

0 0 1 2

样例输入2

12

样例输出2

0 2 2 2

样例输入3

773535

样例输出3

1 1 267 838

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream> //存在超时问题
#include<cmath>
using namespace std;
int main()
{
int N;
cin >> N;
int a, b, c, d;
int n = (int)sqrt(N); //在这里的类型转换中,对sqrt()要实行的是向下取整,所以下面的a<=n不能写成a<n
for(a = 0;a<=n;a++){
for(b = 0;b<=n;b++){
for(c = 0;c<=n;c++){
for(d = 0;d<=n;d++){
// cout << "cur " << a << " " << b << " " << c << " " << d << endl;
if(a*a+b*b+c*c+d*d == N){
cout << a << " " << b << " " << c << " " << d << endl;
return 0;
}
}
}
}
}
return 0;
}

第三题

问题描述

任意给定一个正整数N,如果是偶数,执行: N / 2 如果是奇数,执行: N * 3 + 1
生成的新的数字再执行同样的动作,循环往复。
通过观察发现,这个数字会一会儿上升到很高, 一会儿又降落下来。就这样起起落落的,但最终必会落到“1” 这有点像小冰雹粒子在冰雹云中翻滚增长的样子。
比如N=9 9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1 可以看到,N=9的时候,这个“小冰雹”最高冲到了52这个高度。

输入格式

一个正整数N(N<1000000)

输出格式

一个正整数,表示不大于N的数字,经过冰雹数变换过程中,最高冲到了多少。

样例输入1

10

样例输出1

52

样例输入2

100

样例输出2

9232

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
int main(){
long long n, max, N;
cin >> N;
max = N;
while(N>=1){
n = N;
N--;
while(n!=1){
if(n%2==0)
{
n/=2; //n变小
}
else
{
n=n*3+1;
if(max < n) max = n;
}
}
}
cout << max << endl;
return 0;
}

注意看题目:一个正整数,表示不大于N的数字,经过冰雹数变换过程中,最高冲到了多少。

也就是说仅仅对N求峰值是不够的,对[1, N]都要求峰值,最终输出的max是峰值中的峰值。

第四题

问题描述

给定一个NxN的整数矩阵,小Hi每次操作可以选择两列,将这两列中的所有数变成它的相反数。
小Hi可以进行任意次操作,他的目标是使矩阵中所有数的和尽量大。你能求出最大可能的和吗?

输入格式

第一行一个整数N。
以下N行,每行N个整数Aij。
对于30%的数据,2 ≤ N ≤ 10
对于100%的数据,2 ≤ N ≤ 200, -1000 ≤ Aij ≤ 1000

输出格式

最大可能的和

样例输入

4
-1 1 1 2
-2 -3 1 2
-3 -2 1 2
-4 -1 1 2

样例输出

27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <algorithm>
#include <iostream>
#include <cstring> //用到了memset()
using namespace std;
const int MAX_N = 200;
int main()
{
int matrix[MAX_N];
memset(matrix, 0, sizeof(matrix));
int N;
cin >> N;
int tmp;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
cin >> tmp;
matrix[j] += tmp;
}
}
sort(matrix, matrix + N);
for (int i = 0;matrix[i] + matrix[i + 1]< 0; i += 2)
{
matrix[i] *= -1;
matrix[i + 1] *= -1;
}
int sum = 0;
for (int i = 0; i < N; i++)
{
sum += matrix[i];
}
cout << sum << endl;
return 0;
}

题目的意思是只能变换偶数次,所以第二个for循环中,步长设置为2。

matrix[i] + matrix[i + 1]< 0这个判断条件比较关键,想不到的话不太容易做出来。matrix[i] + matrix[i + 1]如果为负数,则变换后为正,这样sum就能变大了。可以说倒数第二个循环的作用是找到负数项并进行相反数变换,以增大sum。

第五题

问题描述

小Hi写程序时习惯用蛇形命名法(snake case)为变量起名字,即用下划线将单词连接起来,例如:file_name 、 line_number 。小Ho写程序时习惯用驼峰命名法(camel case)为变量起名字,即第一个单词首字母小写,后面单词首字
母大写,例如: fileName 、 lineNumber 。
为了风格统一,他们决定邀请公正的第三方来编写一个转换程序,可以把一种命名法的变量名转换为另一种命名法的变量名。你能帮助他们解决这一难题吗?

输入格式

第一行包含一个整数N,表示测试数据的组数。(1 <= N <= 10)
以下N行每行包含一个以某种命名法命名的变量名,长度不超过100。
输入保证组成变量名的单词只包含小写字母。

输出格式

对于每组数据,输出使用另一种命名法时对应的变量名。

样例输入

2
file_name
lineNumber

样例输出

fileName
line_number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
#include<string> //别忘了加上这个头文件
using namespace std;
int main()
{
int N, end;
cin >> N;
string s;
while(N){
cin >> s;
for(int i = 0;i < s.length();i ++){
if(s[i]=='_'){ //遇到"_",删掉"_",并将随后的"小写字母"变为"大写字母"
s.erase(i,1);
s[i]-=32;
break;
}else if(s[i] < 'a'){ //遇到大写字母,将"大写字母"变为"_小写字母"
s[i] += 32; //变大写字母为小写字母
s.insert(i,1,'_');
break;
}
}
cout << s << endl;
N--;
}
return 0;
}

主要用来考察字符串操作函数的使用吧。只要能对erase(),length(),insert()这些函数熟练使用就ok了。

第六题

问题描述

给定N个整数二元组(X1, Y1), (X2, Y2), … (XN, YN)。
请你计算其中有多少对二元组(Xi, Yi)和(Xj, Yj)满足Xi + Xj = Yi + Yj且i < j。

输入格式

第一行包含一个整数N。
以下N行每行两个整数Xi和Yi。
对于70%的数据,1 ≤ N ≤ 1000
对于100%的数据,1 ≤ N ≤ 100000,-1000000 ≤ Xi, Yi ≤ 1000000

输出格式

一个整数表示答案。

样例输入

5
9 10
1 3
5 5
5 4
8 6

样例输出

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
using namespace std;
const int MAX_N = 100000;
int main()
{
int N;
int matrix[MAX_N][2];
cin >> N;
for(int i = 0;i < N;i++){
cin >> matrix[i][0] >> matrix[i][1];
}
int cnt = 0;
for(int i = 0;i < N;i++){
for(int j = i+1;j < N;j++){
if(matrix[i][0]+matrix[j][0]==matrix[i][1]+matrix[j][1]){
cnt++;
}
}
}
cout << cnt << endl;
return 0;
}

第七题

问题描述

H国的国王有很多王子,这些王子各自也都有很多王孙,王孙又各自有很多后代…… 总之,H国王族的族谱形成了一棵以国王为根的树形结构。根据H国的法律,王族的继承顺位这样规定的:
假设A和B是两位王族

  1. 如果其中一位是另一位的直系父亲、祖先,则辈份高的王族继承顺位更高
  2. 否则,假设C是A和B的最近公共祖先。显然A和B一定是C的两位不同子嗣的后代。其中C较年长的子嗣的后代的继承顺位更高

按时间顺序给出所有国王后代的出生和死亡记录,请你计算所有还活着的后代的继承顺位。

输入格式

第一行包含一个整数N和一个只包含大写字母和数字的字符串,分别代表记录的条数和国王的名字。
以下N行每行包含一条记录:
birth name1 name2 代表name1的儿子name2出生
death name 代表name死亡
1 <= N <= 10000
名字长度不超过20,并且没有重名的王族。

输出格式

按继承顺位从高到低输出每位王族的名字。(不包括国王)
每个名字一行。

样例输入

4 KING
birth KING ALI
birth KING BOB
birth ALI CARRO
death ALI

样例输出

CARRO
BOB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <list>
#include <map>
#include <string>

using namespace std;
struct node
{
string name;
bool alive;
list<node *> sons; //由于node结点可能有不止一个子节点,所以用链表容器(list)存放指向其子节点的指针
node(const string &n, bool a = true) : name(n), alive(a), sons() {}
};

map<string, node *> Node; //用来存储结点名字和指向该结点的指针的映射关系。
// 由于结点的名字不会重复,所以结点的名字可以作为主键,但结点的名字无法定位结点的位置,也无法表明结点之间的关系,所以需要用到指针(node *)

void birth(const string &parent, const string &son)
{
node *king = new node(son); //king是指向新生成的结点的指针
Node[son] = king;
(Node[parent]->sons).push_back(king); //Node[king]是指向名字为parent的结点的指针
}

void death(const string &a)
{
Node[a]->alive = false; //Node[a]是指向名字为a的结点的指针
}

void dfs(node *k)
{
if(k->alive)
{
cout << k->name << endl;
}
list<node *>::iterator i;
for (i = k->sons.begin(); i != k->sons.end(); ++i)
{
dfs(*i);
}
}

int main()
{
int N; //N是记录的条数
string root; //国王的名字
string command, a, b;
cin >> N >> root;
Node[root] = new node(root, false);
for (int i = 0; i < N; i++)
{
cin >> command;
if(command == "birth")
{
cin >> a >> b;
birth(a, b);
}
else if(command == "death")
{
cin >> a;
death(a);
}
}
dfs(Node[root]);
}

如果不是在蓝桥杯的OJ上答题,那么dfs()函数写成这样也是可以的,为什么不能在蓝桥杯OJ上用?因为这种写法用到了C++11标准的一个新特性,而蓝桥杯目前不支持C++11

1
2
3
4
5
6
7
8
9
10
11
void dfs(node *k)
{
if(k->alive)
{
cout << k->name << endl;
}
for (auto i = k->sons.begin(); i != k->sons.end(); ++i)
{
dfs(*i);
}
}

这个dfs()函数中使用了auto来定义循环变量i,这个小技巧是从一位学弟的代码中学来的(果真后浪推前浪哈哈哈)

当时没看明白啥意思,于是网上搜了下auto,发现在C++11标准中,auto关键字在原有基础上,被添加了一种类似其他高级语言的型别推导特性

可以使用auto关键字作为变量的类型声明变量,前提是该变量是被类型明确的初始化变量初始化的

比如int i = 0; auto a = i; //这样a也是int类型了

auto关键字这个新特性在使用模板类的场景下,有助于减少冗赘的代码
不过现在蓝桥杯下这个新特性用不起来,以后应该是会支持的

第八题

问题描述

小Hi的学校大礼堂的地毯是由很多块N × M大小的基本地毯拼接而成的。例如由2×3的基本地毯
ABC
ABD
拼接而成的大礼堂整片地毯如下:

1
2
3
4
5
6
7
8
9
       ...
ABCABCABCABCAB
ABDABDABDABDAB
. ABCABCABCABCAB .
. ABDABDABDABDAB .
. ABCABCABCABCAB .
ABDABDABDABDAB
ABCABCABCABCAB
...

由于大礼堂面积非常大,可以认为整片地毯是由基本地毯无限延伸拼接的。
现在给出K张地毯的照片,请你判断哪些照片可能是小Hi学校大礼堂地毯的一部分。不需要考虑旋转照片的方向。
例如
BCA
BDA
BCA
可能是上述地毯的一部分,但
BAC
BAD
不可能是上述地毯的一部分。

输入格式

第1行包含三个整数,N,M 和 K。
第2~N+1行包含一个N × M的矩阵,代表基本地毯的样式。其中每一个元素都是一个大写字母(A-Z)。
之后是 K 张照片的数据。
每张照片的第一行包含两个整数,H 和 W,代表照片的大小。
以下 H 行包含一个 H × W的矩阵,代表照片中地毯的样式。其中每一个元素都是一个大写字母(A-Z)。
对于80%的数据,1 ≤ N, M ≤ 10, 1 ≤ H, W ≤ 100
对于100%的数据, 1 ≤ N, M ≤ 50, 1 ≤ K ≤ 10, 1 ≤ H ≤ 100, 1 ≤ W ≤ 800。

输出格式

对于每张照片,输出YES或者NO代表它是否可能是大礼堂地毯的一部分。

样例输入

2 3 3
ABC
ABD
3 3
BCA
BDA
BCA
2 3
BAC
BAD
7 14
ABCABCABCABCAB
ABDABDABDABDAB
ABCABCABCABCAB
ABDABDABDABDAB
ABCABCABCABCAB
ABDABDABDABDAB
ABCABCABCABCAB

样例输出

YES
NO
YES

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<iostream>
using namespace std;
const int MAX_N = 50, MAX_M = 50, MAX_H = 100, MAX_W = 800;
bool isPart();
int N, M, K;
int H, W;
char meta[MAX_N][MAX_M]; //用来存储基本样式
char photo[MAX_H][MAX_W];
int main()
{

cin >> N >> M >> K;
for(int i = 0;i < N;i++){ //输入基本样式
for(int j = 0;j < M;j++){
cin >> meta[i][j];
}
}
for(int i = 0;i < K;i++){ //输入照片数据
cin >> H >> W;
for(int m = 0;m < H;m++){
for(int n = 0;n < W;n++){
cin >> photo[m][n];
}
}
if(isPart()) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
bool isPart(){
bool ans;
for(int m = 0;m < H-N;m++){
for(int n = 0;n<W-M;n++){
ans = true;
for(int i = 0;i < N ;i++){ //row
for(int j = 0;j < M;j++){ //column
if(photo[m+i][n+j] != meta[i][j]){
ans = false;
return true;
}
}
}
}
}
return false;
}

使用Cloud Studio打造自己专属的云端开发环境

Cloud Studio是什么

CODING官网说的最清楚:

  • 完全基于浏览器的 IDE(在线集成开发环境)
  • 由CODING自主研发
  • 能免除了繁杂的本地环境搭建,打开浏览器就能写代码

第一次遇到Cloud Studio是在腾讯云开发者平台,前面不是说Cloud Studio是CODING家的吗?不矛盾,根据腾讯云开发者平台官网可得出两者关系:腾讯云开发者平台是腾讯云与 CODING 共同为开发者打造的云端工具平台,这个平台包括的工具有CODING 个人版、Cloud Studio、CODING 企业版

所以说Cloud Studio有着腾讯的support,实际体验了下后,觉得很强。

为什么要用Cloud Studio

  • Visual Studio现在都已经进化到2019了,但机房电脑的VS往往还是9年前的VS 2010

  • 每回上机都要带电脑,对于懒人很不友好

  • 根据腾讯文档推出的的协同编辑功能,可以预感到Cloud Studio将来在结对合作编程上会不断进化

  • 代码云端自动保存,不用再拿U盘copy来copy去了

如何使用Cloud Studio

要使用Cloud Studio就要先创建工作空间,创建项目有两种方式:基于已有项目创建、从模板创建。这里先以从模板创建为例。

注册账号

https://studio.dev.tencent.com/

简单,跳过

新建工作空间

进入dashboard:https://studio.dev.tencent.com/dashboard/workspace

点击新建工作空间。可以选择的选项有:来源、项目、运行环境。这里来源我选腾讯云开发者平台、项目为从模板(Blank)创建。基于这个模板创建的工作空间中,C/C++的开发环境是已经预置好了的。而基于JavaDemo模板创建,则java的开发环境就不用你再自己配置了。

这里我分别基于这两个模板创建了两个工作空间

编写代码

体验挺不错的,界面和功能简单但不简陋。

编译与执行代码

在Cloud Studio中,要编译和执行代码,目前只能通过在终端输入命令来实现。VS Code中也是默认使用这种方式来编译与执行代码的,只不过有了Code Runner这个插件,直接一个ctrl+alt+n就能编译执行了。以下是编译和执行代码的命令。

  • C/C++
1
2
3
4
g++ GuessNumber.cpp -o GuessNumber && ./GuessNumber
# 这段代码做了两件事:
# 用g++命令编译GuessNumber.cpp(源文件),得到可执行文件。其中-o用来指定生成的可执行文件名
# 最后,执行可执行文件

要注意./GuessNumber中的./不能省略,否则会提示找不到可执行文件

另外,如果源文件不是放在/home/coding/workspace下,而是放在其子目录下,则要先cd到源文件所在目录。例如GuessNumber.cpp被放在/home/coding/workspace/trial,则上述命令应该修改为:

1
cd trial && g++ GuessNumber.cpp -o GuessNumber && ./GuessNumber
  • Java
1
2
3
4
javac GuessNumber.java && java GuessNumber
# 这段代码做了两件事:
# 用javac命令编译GuessNumber.java(源文件),得到GuessNumber.class(字节码文件)
# 用java命令执行编译得到的字节码文件

注意点和C/C++一致,不再赘述。下面我们来深入探索下~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
➜  workspace git:(master) ✗ cat /etc/issue # 可见当前这个Java工作空间是基于Ubuntu系统的(之前那个C/C++工作空间也是如此)
Ubuntu 16.04.5 LTS \n \l

➜ workspace git:(master) ✗ sudo apt install tree # 安装tree这个小工具,其作用:list contents of directories in a tree-like format
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed and is no longer required:
openssh-sftp-server
Use 'sudo apt autoremove' to remove it.
The following NEW packages will be installed:
tree
0 upgraded, 1 newly installed, 0 to remove and 48 not upgraded.
Need to get 40.6 kB of archives.
After this operation, 138 kB of additional disk space will be used.
Get:1 http://mirrors.163.com/ubuntu xenial/universe amd64 tree amd64 1.7.0-3 [40.6 kB]
Fetched 40.6 kB in 7s (5,764 B/s)
debconf: delaying package configuration, since apt-utils is not installed
Selecting previously unselected package tree.
(Reading database ... 32133 files and directories currently installed.)
Preparing to unpack .../tree_1.7.0-3_amd64.deb ...
Unpacking tree (1.7.0-3) ...
Setting up tree (1.7.0-3) ...

➜ workspace git:(master) ✗ tree # 查看当前工作空间的目录树
.
├── log.md
├── mvnw
├── mvnw.cmd
├── my
│ ├── demo.java
│ ├── GuessNumber.class
│ ├── GuessNumber.java
│ └── Number.java
├── pom.xml
├── README.md
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── coding
│ │ │ └── studiodemo
│ │ │ └── StudioDemoApplication.java
│ │ └── resources
│ │ ├── application.properties
│ │ └── templates
│ │ └── index.html
│ └── test
│ └── java
│ └── com
│ └── coding
│ └── studiodemo
│ └── StudioDemoApplicationTests.java
└── target
├── classes
│ ├── application.properties
│ ├── com
│ │ └── coding
│ │ └── studiodemo
│ │ └── StudioDemoApplication.class
│ └── templates
│ └── index.html
└── test-classes
└── com
└── coding
└── studiodemo
└── StudioDemoApplicationTests.class

24 directories, 17 files
# 可见目录树是比较复杂的,如果不是实际开发,只是日常写代码,那就用不着这么verbose。所以我创建了my目录,用于上机时随手敲代码。因此使用javac命令编译源文件前,应当cd到源文件所在目录my。
# log.md也是我自己创建的,用于记录对当前工作空间的重要操作,方便日后出问题时溯源

总结

除了Cloud Studio,VS Studio Online也是一个值得观望的云端开发环境。但目前来看,Cloud Studio做得更好。

Hexo的多部署方案

在Hexo的配置文件中,可以通过修改站点的_config.yml配置文件实现一键多部署,即:在使用hexo d时,自动将渲染出的网页文件部署到指定的多个云平台(VPS、GitHub Page等)。

这样有一来就可以在自己的私有云上部署网站,同时可以在公有云(比如GitHub Page)上做个备份。

1
2
3
4
5
6
7
deploy:
- type: git
repo: git@VPS的IP地址:裸仓库名,master
branch: master
- type: git
repo: https://github.com/GitHub用户名/GitHub用户名.github.io
branch: master

配置好后,执行hexo d命令时,Hexo会根据顺序从上到下依次部署。

Hexo框架部署过程

Hexo是个既简洁又强大的博客框架,渲染速度快,原生支持markdown,插件丰富。下面来总结下自己部署Hexo的过程。

本机

在本机部分要做的工作是安装Hexo、Git、npm。这一部分很简单,官方文档说的也很清楚,直接把上面的命令行copy到终端执行就ok了。

需要注意的是,Hexo的官方文档中,中、英文版本并非严格的对照关系,也就是说一部分内容只有英文版的官方文档有,一部分又只有中文版的有,因此可以两边都浏览下以相互补充。

VPS

VPS的操作系统可以考虑CentOS,其他系统也可以,只要把下面的命令换成该系统下的写法就ok了。以下操均是在root下进行,除非特别指定。

安装Git

1
2
3
yum update && apt-get upgrade # 更新内核
git version # 先检查git是否已安装
yum install git

安装nginx

1
2
3
4
yum install nginx
# 如果成功安装,则用浏览器访问服务器的公网IP地址即可看到Nginx的测试页面
systemctl start nginx # 如果nginx已经成功启动,这句就不要执行
systemctl enable nginx # 使Nginx开机自动启动

找到nginx的配置文件

一般nginx的配置文件是/etc/nginx/nginx.conf,另外也可以用如下这个方法来确定nginx的配置文件

1
2
3
4
5
6
7
ps -ef | grep nginx # 在返回的结果中,找到nginx的master process
root 1160 1 0 23:20 ? 00:00:00 nginx: master process /usr/sbin/nginx
nginx 1161 1160 0 23:20 ? 00:00:00 nginx: worker process
root 8853 8836 0 23:24 pts/0 00:00:00 grep --color=auto nginx
/usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

可见nginx的配置文件确实为/etc/nginx/nginx.conf

新建git用户添加sudo权限

1
2
3
4
adduser git # 在CentOS下useradd命令与adduser命令是没有区别的,都是创建用户,在home目录下自动创建新怎用户的home目录,没有设置密码,需要使用passwd命令修改密码
ls -l /etc/sudoers # 查看sudoers文件的属性,并做记录
chmod 740 /etc/sudoers
vim /etc/sudoers

在vi编辑中找到如下内容:

1
2
## Allow root to run any commands anywhere
root ALL=(ALL) ALL

在下面添加一行

1
git   ALL=(ALL)     ALL

保存并退出后执行

1
2
3
chmod 440 /etc/sudoers
ls -l /etc/sudoers # 查看sudoers文件的属性,看看是否成功改回了原先的值
passwd git # 给git用户设置密码(这一步只能在root下进行。可以试试用su git命令切换到git用户,再执行此操作,会发现权限不够)

创建git仓库,并配置ssh登录

1
2
3
4
5
6
7
8
su git # 创建一个裸仓库,裸仓库就是只保存git信息的Repository, 首先切换到git用户确保git用户拥有仓库所有权
cd ~
mkdir .ssh && cd .ssh
touch authorized_keys
vim authorized_keys # 在这个文件中复制粘贴本机的公钥(在id_rsa.pub文件中)
cd ~
mkdir hexo.git && cd hexo.git # 新建hexo.git目录,用来存放裸仓库
git init --bare

测试一下,如果在终端中输入ssh git@VPS的公网IP地址,能够远程登录的话,则表示设置成功了。

创建网站目录并赋予git对网站目录的所有权

这一步我是切换到root用户下操作的(理论上在git用户下操作应该也行吧,只要加上sudo应该就ok,下回试试看看是否可行)

1
2
3
cd /var/www # var目录下的www目录起初如果并不存在,就自行创建
mkdir hexo
chown git:git -R /var/www/hexo

配置git hooks

1
2
3
su git
cd /home/git/hexo.git/hooks
vim post-receive

输入如下内容后保存退出

1
2
3
4
5
6
7
8
#!/bin/bash
GIT_REPO=/home/git/hexo.git #git仓库
TMP_GIT_CLONE=/tmp/hexo
PUBLIC_WWW=/var/www/hexo #网站目录
rm -rf ${TMP_GIT_CLONE}
git clone $GIT_REPO $TMP_GIT_CLONE
rm -rf ${PUBLIC_WWW}/*
cp -rf ${TMP_GIT_CLONE}/* ${PUBLIC_WWW}

然后赋予脚本的执行权限

1
2
3
ls -l post-receive # 查看sudoers文件的属性,并做记录
chmod +x post-receive
ls -l post-receive # 查看sudoers文件的属性,感知下发生了哪些变化

配置nginx

一开始时实际上应该是不存在hexo.conf这个文件的,所以执行vim /etc/nginx/conf.d/hexo.conf命令后,hexo.conf会被自动创建。但直接执行该vim命令权限不够,所以添上sudo。

1
sudo vim /etc/nginx/conf.d/hexo.conf

插入如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80 ;
root /var/www/hexo; # 网站的根目录
server_name xxx.com www.xxx.com; # 注意这里要替换成你自己的域名
access_log /var/log/nginx/hexo_access.log;
error_log /var/log/nginx/hexo_error.log;
location / {
root /var/www/hexo;
if (-f $request_filename) {
rewrite ^/(.*)$ /$1 break;
}
}
location ~* ^.+\.(ico|gif|jpg|jpeg|png)$ {
root /var/www/hexo;
access_log off;
expires 1d;
}
location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
root /var/www/hexo;
access_log off;
expires 10m;
}
}
1
service nginx restart # 重启nginx

至此服务器端就配置好了。下面只要在本机写文章然后用Hexo渲染下,并用git将静态网页push到服务器端就ok了~

需要注意的是,目前服务器只提供了http连接,尚未提供https连接,所以还需要再捯饬捯饬。具体就不展开了,不然这篇文章也太长了。

其他需要注意的

Front-matter怎么写

引用下官方文档对front-matter的定义:

Front-matter is a block of YAML or JSON at the beginning of the file that is used to configure settings for your writings. Front-matter is terminated by three dashes when written in YAML or three semicolons when written in JSON.

也就是说front-matter是这篇文章的配置信息,是放在文章开头,遵循YMAL语法(default)或JSON语法。以下是个示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
title: title
date: yyyy-mm-dd
categories: category
tags: tag
#多标签请这样写:
#tags: [tag1,tag2,tag3]
#或者这样写:
#tags:
#- tag1
#- tag2
#- tag3
---
正文

ssh git@VPS的公网IP 时被拒绝

SSH使用的端口默认为22端口,所以你的VPS需要开启22端口。一般是在安全->防火墙下开启。

应用类型 协议 端口范围
HTTPS TCP 443
SSH TCP 22
HTTP TCP 80

博客的架构

这张架构图是从网上找的,很直观,记不清在哪找的了,感谢作者

Hexo架构图.jpg

整个工作流程可以总结为

  • 本机将markdown文件渲染成静态文件
  • 本机使用git将静态文件推送到服务器的裸库
  • 服务器再通过git-hooks同步网站根目录
  • 访客通过nginx访问服务器上的静态文件

用WDS技术拓展家庭网络覆盖范围

甲:恭喜搬新家啊老哥,真羡慕你这双层的户型。

乙:咳,别提了,二楼的网速差着呢。从GitHub上clone一个repo要老半天,还常常因为网速过慢而clone失败。

甲:那就很头疼了,git不支持断点续传呀。

乙:可不是!所以在我家二楼,clone理论可行实际不可行。

甲:主要是二楼离路由器远,要不你离近点。

乙:总不能每回上个网都把电脑手机平板搬下楼吧?

甲:倒也是,那该怎么解呢?

别着急,路由器的无线桥接功能可以帮到你。这个功能可以让主副路由器通过无线进行桥接,从而扩展无线覆盖范围。

无线桥接功能又称WDS功能,因为其基于WDS技术。WDS技术听着听玄乎,其实原理super easy!

原理介绍

WDS(Wireless Distribution System)技术,翻译过来就是无线分布式系统技术。

终端设备(手机、平板、电脑、无线智能摄像头等)和路由器的连接其实很像对话。在R、E两人分处两个山头的情况下(终端设备和路由器之间距离太远),对话起来会很不容易(网路慢),甚至有时卖命喊也未必能让对方听到(丢包)。这时,一位热心的小伙伴H(副路由器)闪亮登场了,他做了一件很普通的“小事”,却给双方的对话质量带来了不小的改观,我把这件“小事”称为接力

有了H(副路由器)后,R(路由器)的话传给H(副路由器),H(副路由器)再接力给E(终端设备),这样就帮R拓展了传话范围,H对于E的帮助是类似的。也就是说,WDS技术能拓展现有路由器(主路由器)的覆盖范围,以消除无线盲点,提升信号较弱区域的信号强度。

把WDS技术应用到家庭网络上,那么即便我们上阁楼或者下地下室,也能享受到wifi的恩泽~

那么下面我们就来看看怎样开启路由器的无线桥接功能。

步骤

在哪台路由器上开启无线桥接功能,哪台路由器就作为副路由器。也就是说,无线桥接的设置过程是在副路由器侧完成的。所以副路由器必须支持无线桥接功能,但主路由器不必支持。

  1. 进入副路由器管理页面

    一般路由器底部都会管理页面的url,在电脑浏览器上打开即可。然后在应用管理下找到无线桥接功能。

    1571211657078.png

    1571211694415.png

  2. 选取桥接设备

    也就是为当前的副路由器选择需桥接的主路由器,具体的选取方式有两种:扫描添加手动添加。一般是用扫描添加,如果你家的主路由器关闭了无线广播,那就得手动添加了。添加过程中需要输入主路由器的无线密码。

    1571211842136.png

    1571211919109.png

  3. 设置无线参数

    这里的无线参数是指副路由器的无线名称和无线密码。向导页面会建议你将副路由器的无线参数更改为与主路由器一致,这样就能实现漫游功能。就是说终端设备在移动过程中(比如你把平板带到二楼,平板上这在下载电影),你的设备可以自动切换所使用的路由器(下载不会中断),这就实现了漫游。如果对漫游没有需求,那主副路由器的无线名称和无线密码设置得不一样也是可以的,网上不少教程在这里都说成必须设置成一样的。

至此无线桥接就成功开启了。

注意点

以下注意点参考自TP-LINK帮助文档

  • 这里我使用的路由器是TL-WDR5620千兆版,相对来说各方面还是比较强的。如果你选用的桥接路由器是淘宝特价版的那种,虽然应该也会有无线桥接功能,但将路由器设置为无线桥接路由后,就无法进入管理页面了,无可玩性可言。应该也可以通过一定方式进入,知道的朋友欢迎和我分享下经验。

  • 在设置副路由器过程中,副路由器內建的DHCP服务器(用于自动配置局域网中各计算机的TCP/IP协议)会自动关闭,避免终端设备从副路由器获取IP地址。这一步在大部分比较新的路由器上都是自动完成的,这样就简化了设置流程,减轻了用户的学习成本。但作为学习,可以在设置好副路由器后,进入副路由器管理页面验证下。

  • 副路由器的LAN口IP地址默认从主路由器自动获取,这样做是为了避免手动设置导致的IP地址冲突(IP地址冲突的后果是网络中的终端设备无法上网),所以要确保主路由器的DHCP服务器已开启。无线桥接功能成功开启后,大家可以进入副路由器后台页面看看其LAN口IP地址,正常情况下是和主路由器的LAN口IP地址不一样,但仍然在同一网段。

  • 如果主路由器如果开启了无线设备接入控制,但是允许接入设备列表中没有副路由器的MAC地址,会导致无线桥接失败

  • 如果你家是个超级无敌大的大别墅,那么两台路由器可能也不够用,这时就需要进行多台路由器的桥接。拓扑结构主要有3种:

    • 星型拓扑:每个副路由器都是和主路由器之间桥接
    • 直线型拓扑:路由器之间组成类似双向链表的结构,适用于狭长区域,建议桥接不超过三级,否则双向链表(共n个结点)中,第二个结点对应的路由器坏了,那么该结点和后面的结点(共n-1个结点)对应的路由器都无法正常工作
    • 树型拓扑
  • 环路会导致广播风暴,从而影响到电脑的正常上网。 使用WDS桥接后,主、副路由器之间不可以再连接网线。如果主副路由器都是双频路由器,5GHz和2.4GHz不能同时进行WDS桥接。正确的桥接如下图:

    20171201170547_1752.png

    20171201170555_4276.png

有了WDS技术,从此你的奇思妙想不再为网速所限,拿好你的副路由器,哪里不会点哪里哪里网速差就在哪安置一个,哈哈哈~~~