降低 Kali Linux 资源占用:配置使用 Xfce 4 桌面环境

前言

Xfce Desktop Environment website:
https://www.xfce.org/

Xfce-维基百科:
https://zh.wikipedia.org/wiki/Xfce

Xfce 不是最华丽的 Linux 桌面环境,但却是相当节省系统资源的 Linux 桌面环境。就我个人的使用习惯而言,在 Linux 上,特别是 Linux 虚拟机中使用 Xfce 桌面是一个极佳的选择,因为这样可以把有限的系统资源更多地用于处理生产性作业。
Xfce 最新的版本是 “4.12”.
Kali Linux 2019.1 以及该版本之前的一些版本使用的都是 Gnome 桌面环境而不是 Xfce, 因此,本文就从降低系统资源占用的角度出发,介绍一下将 Kali 的桌面环境从 Gnome 更换到 Xfce 4 的过程。

过程

安装 Xfce 4,命令:

apt install kali-defaults kali-root-login desktop-base xfce4 xfce4-places-plugin xfce4-goodies

在安装的过程中(可能)会要求我们选择提供图形化系统登录界面的图形管理程序(A display manage is a program that provides graphical login for the X Windows System.),只有一个显示管理器可以管理给定的 X server (X server 是用于管理图形化界面的服务器端程序),但是如果当前系统中安装了多个显示管理器包,就需要选择一个默认的显示管理器(Only one display manage can manage a given X server, but multiple display manager packages are installed. Please select which display manager should run by default.),这里我选择的默认显示管理器是 “gdm3”, 如图 1:

图 1
图 1

切换桌面:

update-alternatives --config x-session-manager

回显如下:

root@kali:~# update-alternatives --config x-session-manager
There are 3 choices for the alternative x-session-manager (providing /usr/bin/x-session-manager).

  Selection    Path                    Priority   Status
------------------------------------------------------------
* 0            /usr/bin/gnome-session   50        auto mode
  1            /usr/bin/gnome-session   50        manual mode
  2            /usr/bin/startxfce4      50        manual mode
  3            /usr/bin/xfce4-session   40        manual mode

Press <enter> to keep the current choice[*], or type selection number:

输入 3 以启动 Xfce4,回显如下:

root@kali:~# update-alternatives --config x-session-manager
There are 3 choices for the alternative x-session-manager (providing /usr/bin/x-session-manager).

  Selection    Path                    Priority   Status
------------------------------------------------------------
* 0            /usr/bin/gnome-session   50        auto mode
  1            /usr/bin/gnome-session   50        manual mode
  2            /usr/bin/startxfce4      50        manual mode
  3            /usr/bin/xfce4-session   40        manual mode

Press <enter> to keep the current choice[*], or type selection number: 3
update-alternatives: using /usr/bin/xfce4-session to provide /usr/bin/x-session-manager (x-session-manager) in manual mode

卸载 Gnome 桌面环境:

apt remove gnome-core
apt remove gnome-shell

重启系统:

reboot

重新登录系统之后可以看到已经成功地将 Kali Linux 的桌面环境更换为 Xfce 4,如图 2:

图 2
图 2

探秘维基解密网站的服务器和域名

2019 年 04 月 11 日,维基解密的创始人朱利安·阿桑奇因为失去了厄瓜多尔政府的政治庇护,在位于伦敦的厄瓜多尔驻英国大使馆中被英国警方逮捕。

图 1 由David G Silvers. – https://www.flickr.com/photos/dgcomsoc/14770416197/,CC BY-SA 2.0,https://commons.wikimedia.org/w/index.php?curid=34813282

维基解密网站成立于 2006 年 12 月,至今该网站仍然在运作并可以通过国际互联网访问。

图 2 维基解密网站(https://wikileaks.org/)首页截图
图 3 维基解密网站(https://wikileaks.org/) LOGO

于是我就在想,维基解密使用的服务器在哪里?域名如何解析的?

带着这些疑问,我使用一台位于国外的 VPS, Ping 了一下 “wikileaks.org” 这个域名,测试结果如下:

图 4

由图 4 可知,解析到的 “wikileaks.org” 这个域名所在的服务器的 IP 地址是 ”195.35.109.53”.

随后,我在 Ipinfo(https://ipinfo.io/) 这个网站查询了 “195.35.109.53” 这个 IP 的信息(https://ipinfo.io/195.35.109.53),查询结果如图:

图 5

根据查询结果显示, “195.35.109.53” 这个 IP 地址的托管机构是位于挪威奥斯陆的 Blix Solutions AS.

Blix Solutions AS 的官网首页如图:

图 6

在 “dnslytics.com” 上查询 “wikileaks.org” 可以获得如下信息(https://dnslytics.com/domain/wikileaks.org):

域名 “wikileaks.org” 的基础信息:

图 7

Whois 信息:

图 8

NS 记录:

图 9

MX 记录:

图 10

在 dnslytics.com 上查询 IP 地址 “195.35.109.53” 可以看到(https://dnslytics.com/ip/195.35.109.53), 该 IP 地址和域名 “wikileaks.org” 之间的 DNS 解析服务由 “host1.no” 提供,HOST1 是一家位于挪威奥斯陆的网络公司,提供虚拟主机,云服务器和域名购买服务等,其官网首页如图:

图 11

此外,我们在 dnslytics.com 提供的查询结果中还可以看到,有 6 个域名都解析到了IP 地址为 “195.35.109.53” 的这台服务器上,这些域名应该都是属于维基解密的。

图 12

在中文维基百科中可以找到如下关于维基解密网站所使用的主机和域名的信息:

主机

维基解密形容自己为“一个对大量来源不明的泄密文件进行审查的系统”。该网址可被多个服务器使用且不同的域名伴随着大量的阻断服务攻击,同时它的切断服务来自不同的域名解析系统提供者。

直到2010年8月,维基解密一直使用PRQ的主机,一个提供“高度安全性,零故障的主机服务”的瑞典公司。据说PRQ公司“几乎没有关于其客户的信息,很少进行维护”。维基解密网站托管在以坚持客户匿名著称的瑞典网络服务提供商PRQ.se,这家公司可以承受法律压力和网络攻击。先将文件送到位于比利时的服务器上,再送到位于“另一个法律上较友善的国家”的服务器上,然后这些文件被转存到其他地方后删除,。由一批匿名的工程师提供技术维护,整个流程和提交的文件都被加密,并使用经过修改的Tor网络匿名传输,整个系统即使核心成员也无法全部进入。此外,维基解密还在系统中一直传递许多虚假的提交文件,以使真正的文件难以被发现。

现在,维基解密的主机主要由建在一座核掩体中的Bahnhof公司提供。其他服务器以瑞典服务器为中心分布于全世界。阿桑奇曾说服务器设置在瑞典(及其他国家)“主要是因为这些国家能为这个网站的泄密行为提供法律保护”。他认为瑞典宪法给予了那些信息提供者完全的法律保护。在瑞典,政府对任何类型的报纸的消息来源的问询都是被禁止的。这些法律,以及PRQ的主机,让任何一个政府迫使维基解密下线的行动都变得困难,它们设置了一个能抵御来自任何牢骚者的诉讼的保护伞,保护了维基解密的自由。维基解密在秘密地点维护他们的服务器、删除服务器日志并使用军事级别的加密技术来保护信息源和其它机要信息。”该种服务器组织形式被称为“防弹主机”。

2010年8月17日,一则消息被宣布,瑞典海盗湾将提供并管理多台维基解密的新服务器。海盗湾捐赠了免费的服务器和宽带给维基解密。该机构技术员将保证这些服务器的维护和工作。

在这个放置在旧服务器上的地址变成一个黑客拒绝式服务攻击的目标后,维基解密把其地址移到了亚马逊的服务器上。然而随后不久,该网址在亚马逊服务器上被“废止”。在一份公开声明中,亚马逊说维基解密未遵守它的服务条款。公司进一步解释说,“它们违反了条款的几个部分。比如,我们的服务条款声明‘你显示或授权显示的内容,不能用于支持违法或者可能导致某人或法律实体受到伤害的行为。’显然,维基解密显示了这种内容。”之后维基解密决定安装由法国OVH公司提供的属于维基解密自己的服务器。在受到来自法国政府的批评人士的刁难后,该公司转于寻求两个法院进行针对为维基解密提供服务器案的裁决。当里尔的法院迅速倾向于迫使OVH关闭维基解密网站时,巴黎法院声明他们需要更多的时间来研究高技术议题。

维基解密建立在几个软件包的基础上,包括Tor, PGP, MediaWiki以及Freenet。维基解密积极鼓励通过Tor发送信息,因为它能满足用户强烈的保密需求。

2010年11月4日,阿桑奇告诉瑞士电视台TSR他急需在中立的瑞士寻求政治庇护并建立一个维基解密基金以把各种工作移到这里。按阿桑奇所言,瑞士和冰岛是仅有的几个能让维基解密感到可以安全地工作的国家。

域名服务器

维基解密曾经使用EveryDNS的域名解析服务,该域名服务器曾受到DDoS攻击。攻击影响了EveryDNS的服务质量,故而该公司撤除了对维基解密的服务。资深的维基解密支持者通过一次DDoS攻击报复了EveryDNS。此外,由于博客中的错误,一些支持者误将EasyDNS当做EveryDNS,随后数量可观的支持者参与了对EasyDNS的抵制,最终迫使EasyDNS决定提供域名解析服务给维基解密。

https://zh.wikipedia.org/wiki/維基解密

根据上面的信息,维基解密曾使用过 PRQ 公司提供的主机服务,我搜索了一下,确实存在一个使用了 “PRQ.se” 域名并且经营主机服务的公司,其官网首页如图:

图 13

今天的探秘就到这里了。说是“探秘”其实也不准确,因为上面这些信息都是公开的,不过我之前确实不知道这些信息,所以我了解了一些之后就把我查到的资料放在这里与大家分享一下,满足一下小小的好(偷)奇(窥)心(欲)。

C / C++ 中的计时函数: clock()

clock() 函数是 C 标准库 time.h 中的一个函数, time.h 标准库中定义了各种涉及日期和时间的函数, 变量类型和宏. 其中, clock() 函数可以返回自程序开始执行到当前位置为止, 处理器走过的时钟打点数(即”ticks”, 可以理解为”处理器时间”). 在 VC++6.0 中, 每过千分之一秒(即 1 毫秒)则 clock() 函数的返回值加 1. 但是, 处理器的时钟打点数并不是一个人类可以直观感知的时间概念, 时钟打点数只描绘了该处理器在处理该问题时所耗费的”处理器时间”. 为了能将获取到的时间转换成便于人类理解且具有普遍性的”时 分 秒”的计时方式, 我们需要引入一个常量, 在 VC++6.0 中, 使用常量 CLOCKS_PER_SEC 来进行转换且 CLOCKS_PER_SEC=1000.
但是在不同的编译环境中, CLOCKS_PER_SEC 的数值可能是不同的. 在 Windows 10 中使用”Everything”这个程序在本机上搜索 stdio.h 可以看到我的这个计算机操作系统中存在”Emacs”, “CodeBlocks”和”Dev-cpp”三款编译器, 因此也就存在三套相互独立的 C 语言编译环境, 如图 1:

图 1

(下面以 Windows 10 下的 CodeBlocks 的编译环境为例, 查看在 C 语言的头文件中关于 CLOCKS_PER_SEC 的定义.)

在我的电脑上, CodeBlocks 的 C 语言头文件位于下面的位置:

C:\GreenSoftware\codeblocks-17.12mingw-nosetup\MinGW\include

在这个目录下找到 time.h 这个头文件, 可以在其中找到如下关于 CLOCKS_PER_SEC 的定义:

/*
 * Number of clock ticks per second. A clock tick is the unit by which
 * processor time is measured and is returned by 'clock'.
 */
#define    CLOCKS_PER_SEC  ((clock_t)1000)
#define    CLK_TCK     CLOCKS_PER_SEC

由上面的头文件中的定义可以知道, 常量 CLOCKS_PER_SEC 的值被定义为 1000, 变量类型为 clock_t.

为了验证, 我们可以在 Windows 平台上的 CodeBlocks 中运行如下 C++ 程序:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;

int main()
{
    int i=10;
    clock_t start,finish;
    double Times, Times1;
    start=clock();
    while(i--){
        cout<<i<<endl;
    };
    finish=clock();
    Times=(double)(finish-start)/CLOCKS_PER_SEC;
    Times1=(double)(finish-start)/CLK_TCK;
    cout<<"start(时钟打点): "<<start<<endl;
    cout<<"finish(时钟打点): "<<finish<<endl;
    cout<<"CLOCKS_PER_SEC: "<<CLOCKS_PER_SEC<<endl;
    cout<<"CLK_TCK: "<<CLK_TCK<<endl;
    cout<<"运行时间(秒)(CLOCKS_PER_SEC): "<<Times<<endl;
    cout<<"运行时间(秒)(CLK_TCK): "<<Times1<<endl;

    return 0;
}

控制台的输出结果是:

9
8
7
6
5
4
3
2
1
0
start(时钟打点): 5
finish(时钟打点): 6
CLOCKS_PER_SEC: 1000
CLK_TCK: 1000
运行时间(秒)(CLOCKS_PER_SEC): 0.001
运行时间(秒)(CLK_TCK): 0.001

Process returned 0 (0x0)   execution time : 0.322 s
Press any key to continue.

根据上面的输出结果也可以证明在 Windows 平台上的 CodeBlocks 编译器中常量 CLOCKS_PER_SEC 的值被定义为 1000.

注: 在 VC++6.0 环境中, CLK_TCKCLOCKS_PER_SEC 均被定义成 1000. 因此, 一般情况下, 在 Windows 环境中, 程序里使用 CLK_TCK 或者 CLOCKS_PER_SEC 的效果是一样的, 但是, 在 Linux 环境中只能使用 CLOCKS_PER_SEC.

Linux 下对于 CLOCKS_PER_SEC 这个常量的定义一般和 Windows 下是不同的.

Linux 下的 C 语言头文件通常都是放在 /usr/include 这个目录下.

(下面, 我将使用”Kali Linux 2019.1″进行本部分接下来的操作.)

/usr/include 目录下找到并打开 time.h, 在其中可以看到如下内容:

/* This defines CLOCKS_PER_SEC, which is the number of processor clock
   ticks per second, and possibly a number of other constants.   */
#include <bits/time.h>

在这个头文件里没有直接指明 CLOCKS_PER_SEC 的值, 而是选择包含了 bits/time.h 这个头文件. 于是, 我们在 /usr/include/x86_64-linux-gnu/bits 目录下找到 time.h 文件, 可以找到如下内容:

/* ISO/IEC 9899:1999 7.23.1: Components of time
   The macro `CLOCKS_PER_SEC' is an expression with type `clock_t' that is
   the number per second of the value returned by the `clock' function.  */
/* CAE XSH, Issue 4, Version 2: <time.h>
   The value of CLOCKS_PER_SEC is required to be 1 million on all
   XSI-conformant systems. */
#define CLOCKS_PER_SEC  ((__clock_t) 1000000)

由此我们知道, 在该 C 语言编译环境下, CLOCKS_PER_SEC 的值被定义成 1000000.

同样地, 在 Linux 下运行如下程序也可以看到当前编译环境下 CLOCKS_PER_SEC 的数值是多少:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
using namespace std;

int main()
{
    int i=10;
    clock_t start,finish;
    double Times;
    start=clock();
    while(i--){
        cout<<i<<endl;
    };
    finish=clock();
    Times=(double)(finish-start)/CLOCKS_PER_SEC;
    cout<<"start(ticks): "<<start<<endl;
    cout<<"finish(ticks): "<<finish<<endl;
    cout<<"CLOCKS_PER_SEC: "<<CLOCKS_PER_SEC<<endl;
    cout<<"Total Times(s)(CLOCKS_PER_SEC): "<<Times<<endl;

    return 0;
}

运行后, 控制台的输出结果是:

9
8
7
6
5
4
3
2
1
0
start(ticks): 1124
finish(ticks): 1169
CLOCKS_PER_SEC: 1000000
Total Times(s)(CLOCKS_PER_SEC): 4.5e-05

另外, 在 time.h 这个 C 标准库中, 还定义了四个库变量, 其中库变量 clock_t 的作用是存储处理器时间, 因此, 在声明 clock() 函数的时候需要使用 clock_t 声明函数的返回值类型.

使用 clock() 计算程序中一个函数运行耗时的模板如下:

#include <stdio.h>

#include <time.h>
/*
导入 clock() 函数的头文件.
*/

clock_t start, stop;
/*
定义记录开始和结束时间的变量.
clock_t 是 clock() 函数的返回变量类型.
*/

double duration;
/*
记录函数运行时间, 单位为秒.
*/

int main(){

    start=clock();
/*
记录自 main() 函数被执行开始到本次 clock() 被调用一共走过了多少个 ticks.
*/

    MyFunction();//要进行计时的目标函数.

    stop=clock();
/*
记录自 main() 函数被执行开始到本次 clock() 被调用一共走过了多少个 ticks.
*/

    duration=((double)(stop-start))/CLOCKS_PER_SEC;
    //将时钟打点数转换成人类可以直观感知的时间秒数.

    /*
    不在测试范围内的操作写在这里, 例如输出 duration 的数值的操作等.
    */

    return 0;
}

在 Kali 和 Ubuntu 上安装 Code Blocks

操作环境

Kali 2019.1

操作步骤

添加软件源:

root@kali:~# add-apt-repository ppa:damien-moore/codeblocks-stable

更新软件源:

root@kali:~# sudo apt-get update

安装 Code Blocks:

root@kali:~# sudo apt-get install codeblocks

启动 Code Blocks:

root@kali:~# codeblocks

之后, 可以参考我这篇博文对 Code Blocks 的外观进行优化, 文章地址:
http://zhaokaifeng.com/?p=1590

Kali Linux 忘记系统登录密码后的重置密码操作

前言

如果忘记了系统的登录密码, 最关键的需求就是重置 root 用户的登录密码, 之后使用 root 账户可以修改其他账户的密码. 因此, 本文就介绍一下在不知道 root 用户登录密码的情况下如何重置 root 账户的密码.

操作环境

Kali Linux 2019.1

操作步骤

启动 Kali Linux, 在启动了 GNU GRUB (GNU 引导) 界面时按 e 进入编辑模式.
之后找到如图 1 所示的位置, 把其中的 ro 改成 rw, 并删除 .gz 后面的 quite, 替换成 init=/bin/bash.

图 1
图 1

修改后如图 2 所示:

图 2
图 2

修改完成之后, 使用 Ctrl +X 组合键继续系统的启动进程, 此时是可以无密码直接进系统的, 如图 3:

图 3
图 3

在图 3 中所示的 # 提示符后面输入重置密码的 passwd 命令即可修改 root 用户的密码并使用修改后的密码正常登陆系统.

对使用 Z-BlogPHP 搭建的网站进行安全加固 (上)

前言

Z-BlogPHP 是一款常用的 Web 博客程序, 许多网站都是使用 Z-BlogPHP 搭建的. 今天, 我来结合自己目前所掌握的知识谈谈对使用 Z-BlogPHP 搭建的网站进行安全加固的方法. 希望本文能让更多的网站尽可能地远离安全威胁.

网站加固涉及很多方面, 有 Web 程序的加固, 中间件的加固, 代码运行环境的加固, 还有 Web 服务器的加固等, 本文只讨论对 Web 程序, 也就是 Z-BlogPHP 本身的加固.

操作环境

Z-BlogPHP 版本: Z-BlogPHP 1.5.2 Zero 正式版

加固步骤

保护登录入口

使用 Google 两步验证

两步验证相当于给登录过程加了两把锁, 即使我们网站的登录密码被恶意获取和利用, 攻击者也无法仅仅依靠该密码就成功登录网站后台. 两步验证的实现方式中常用的大致有两种:

  • 手机短信验证码
  • 虚拟 MFA

使用”手机短信验证码”的方式进行验证就是在登录的时候除了要输入用户名和密码之外还会要求输入一个由服务器发来的手机短信. 虚拟 MFA 的作用和手机短信验证码类似, 只不过虚拟 MFA 可以基于时间 (或者计数器) 生成验证码 (每 30 秒自动生成一个 6 位数字), 不需要联网即可运作.

目前可以提供虚拟 MFA 验证的软件有:

  • Authy
    官网: https://authy.com/
    Authy 提供的验证程序几乎覆盖了目前所有主流操作系统, 其支持的平台有 iOS, Android, Windows, macOS 以及 Chrome 浏览器.
  • 1Password
    官网: https://1password.com/
    1Password 是一个密码管理器, 也支持两步验证.
  • Microsoft Authenticator
    官网: https://www.microsoft.com/en-us/account/authenticator
    微软出品的两步验证程序.
  • Google 2-Step Verification
    官网: https://www.google.com/landing/2step
    Google 出品的两步身份验证器.

想要在 Z-BlogPHP 中使用两步验证, 首先需要在 Z-BlogPHP 的应用中心搜索”Google两步验证”找到相关插件, 插件 ID 是”LiangbuLogin”. 下载完成之后, 启用该插件.

下面我在手机上使用 Google 两步验证程序”Google 身份验证器”和 Z-BlogPHP 中的”Google两步验证”插件演示配置并启用两步验证的过程.

首先, 在 Z-BlogPHP 的管理后台中进入”Google两步验证”插件的配置界面并打开手机上的”Google 身份验证器”, 之后的操作过程如图 1 所示:

图 1
图 1

之后, 再登陆 Z-BlogPHP 的后台时会要求输入 Google 两步验证的验证码, 如图 2 所示.

图 2
图 2

如果进行两步验证的设备丢失, 可以在服务上删除如下目录及文件即可取消 Google 两步验证:

XX/zb_users/plugin/LiangbuLogin

另外, 需要注意的是, 如果设备支持的话, 请务必给进行两步验证的程序加上应用锁以尽可能避免未经授权的使用.

修改默认登录地址

Z-BlogPHP 默认的登录地址如下:

https://域名/zb_system/login.php

如果使用默认的登录地址的话, 就是把网站登录入口直接暴露在攻击者面前, 这是一个严重的不安全因素. 下面演示修改默认登录地址的方法.

首先将 zb_system/ 目录下的 login.php 文件修改成一个随机名称, 例如: ajdvirb6zmg9s7k2.php, 之后, 登录地址就变成了:

https://域名/zb_system/ajdvirb6zmg9s7k2.php

下一步, 在当前网站所使用的主题 (一般位于 xx/zb_users/theme/ 目录下) 下面找到 include.php 这个文件, 在其中加入如下 PHP 代码:

#修改默认登录地址
function zpnfewmcbxgsak(){
   global $zbp;
   $sfwidmsahdgv="ssocnf7sbsnxs";
   $dsdsuacskpqba="sdvn2ps3m2n";
   if($_GET[''.$sfwidmsahdgv.''] !== ''.$dsdsuacskpqba.'') {
      Redirect($zbp->host);
      die();
   }
}

之后, 同样在 include.php 这个文件中, 找到 function ActivePlugin_主题 ID(){} 函数, 在其中加入如下 PHP 代码:

Add_Filter_Plugin('Filter_Plugin_Login_Header','zpnfewmcbxgsak');

经过上述操作之后, 登录地址就变成了:

https://域名/zb_system/ajdvirb6zmg9s7k2.php?ssocnf7sbsnxs=sdvn2ps3m2n

上面地址中的任意一个字符不对都将无法打开登录页面.
如果忘记了登录地址, 只需要进入服务器, 找到上面所作的更改并组合成登录地址即可.

需要注意的一点是, Z-BlogPHP 默认会把后台登录地址显示在网站前台, 这个组件一定要去掉.

修改登录页面默认标题和内容

通常情况下, 登录页面都会有一些和登录相关的关键词, 例如”登录”, “用户登录”, “login”, “网站后台”, “用户名”和”密码”等. 如果登录界面被搜索引擎爬取到了, 那么攻击者通过类似”Google hack”这样的方式就有可能获取网站登录后台的地址. 因此, 有必要把登录页面的一些关键词修改成和”登录”以及”网站后台”无关的内容. 修改方法如下:

打开 zb_system/ 目录下的 login.php 文件(如果您按照上面的过程进行了修改, 那么此时, 这个文件的名称应该是 ajdvirb6zmg9s7k2.php, 具体名称以您的修改操作为准.), 进行如下修改:

修改用户名输入框的提示字符, 将:

<dd class="username"><label for="edtUserName"><?php echo $lang['msg']['username'] ?></label><input type="text" id="edtUserName" name="edtUserName" size="20" value="<?php echo GetVars('username', 'COOKIE') ?>" tabindex="1" /></dd>

修改成:

<dd class="username"><label for="edtUserName"><?php echo "hskVex" ?></label><input type="text" id="edtUserName" name="edtUserName" size="20" value="<?php echo GetVars('username', 'COOKIE') ?>" tabindex="1" /></dd>

修改密码输入框的提示字符, 将:

<dd class="password"><label for="edtPassWord"><?php echo $lang['msg']['password'] ?></label><input type="password" id="edtPassWord" name="edtPassWord" size="20" tabindex="2" /></dd>

修改成:

<dd class="password"><label for="edtPassWord"><?php echo "sFhfc" ?></label><input type="password" id="edtPassWord" name="edtPassWord" size="20" tabindex="2" /></dd>

修改页面标题, 将:

<title><?php echo $blogname . '-' . $lang['msg']['login'] ?></title>

修改成:

<title><?php echo "HFxs&3^k" . '-' . "pJ18&qxF" ?></title>

修改登录界面的图片标题和图片说明, 将:

<div class="logo"><img src="image/admin/none.gif" title="<?php echo htmlspecialchars($blogname) ?>" alt="<?php echo htmlspecialchars($blogname) ?>"/></div>

修改成:

<div class="logo"><img src="image/admin/none.gif" title="<?php echo "DshjFac@1A7%" ?>" alt="<?php echo "DsE2vshKw7" ?>"/></div>

修改”保持登录”提示字符, 将:

<dd class="checkbox"><input type="checkbox" name="chkRemember" id="chkRemember"  tabindex="98" /><label for="chkRemember"><?php echo $lang['msg']['stay_signed_in'] ?></label></dd>

修改成:

<dd class="checkbox"><input type="checkbox" name="chkRemember" id="chkRemember"  tabindex="98" /><label for="chkRemember"><?php echo "Qh1A2s$sAv%gC*" ?></label></dd>

修改登录按钮, 将:

<dd class="submit"><input id="btnPost" name="btnPost" type="submit" value="<?php echo $lang['msg']['login'] ?>" class="button" tabindex="99"/></dd>

修改成:

<dd class="submit"><input id="btnPost" name="btnPost" type="submit" value="<?php echo "HCL9" ?>" class="button" tabindex="99"/></dd>

记录访问登录页面的 IP 地址

对于记录 IP 地址这个功能, 我们可以使用第三方的网站统计服务来实现. 我们可以申请一个统计代码, 而且只将这个统计代码放在登录页面, 这样就可以记录到所有访问过登录界面的 IP 地址, 根据这些 IP 地址和登陆时间就可以大致判断网站的登录界面是否已经被攻击者掌握. 但是, 使用第三方服务也就意味着我们的后台登录地址将上传到第三方平台的服务器, 从而增大了后台登录地址可能受到攻击的攻击面, 在这里我们通过一段 PHP 代码实现对访问了登录页面的 IP 地址进行记录的功能.

打开 zb_system/ 目录下的 login.php 文件(如果您按照上面的过程进行了修改, 那么此时, 这个文件的名称应该是 ajdvirb6zmg9s7k2.php, 具体名称以您的修改操作为准.), 添加如下代码:

<?php
$ip = $_SERVER["REMOTE_ADDR"];
$var = "$ip;\n";

file_put_contents('ip.md', $var, FILE_APPEND | LOCK_EX);
?>

上述代码将在当前路径下创建 ip.md 文件并将带有当前访问者 IP 地址的变量 $var 的值以追加的方式 (使用参数 FILE_APPEND 实现内容追加) 写入到 ip.md 文件. 今后, 我们可以通过 ip.md 文件记录的内容获知是否有未授权的 IP 地址打开过登录界面.

使用强登录密码

这一点无需多言, 登录密码至少需要有 16 位, 且必须包括大小写字母, 数字和特殊字符并经常更换.

启用 Z-BlogPHP 自带的安全设置

在 Z-BlogPHP 的管理后台的”网站设置 / 全局设置”中开启”安全增强”并关闭”开发模式”, 如图 3:

图 3
图 3

在安装完必要的插件之后, 在 Z-BlogPHP 的管理后台的”应用中心 / 安全模式”中开启安全模式, Z-BlogPHP 建议我们不要使用可以进行文件管理, 数据库管理, 主题编辑和网站备份的插件, 如图 4:

图 4
图 4

如果要退出安全模式, 需要进入网站的服务器端, 手动删除以下文件:

/zb_users/data/appcentre_security_mode.php

之后, 在网站后台中进入”插件管理”, 停止”应用中心客户端”的运行. 今后只需要每隔特定时间检查一次更新或者在获知有新版本的程序发布时开启”应用中心客户端”进行一下更新即可.

防止恶意上传

由于我使用 Z-BlogPHP 搭建的网站中主要使用的图片是 .webp 格式, 除此之外基本没有上传过其他格式的文件, 因此可以在后台去掉对不常用的文件格式的上传支持. Z-BlogPHP 默认支持的上传格式如下:

jpg|gif|png|jpeg|bmp|psd|wmf|ico|rpm|deb|tar|gz|sit|7z|bz2|zip|rar|xml|xsl|svg|svgz|rtf|doc|docx|ppt|pptx|xls|xlsx|wps|chm|txt|pdf|mp3|mp4|avi|mpg|rm|ra|rmvb|mov|wmv|wma|swf|fla|torrent|apk|zba|gzba

我修改后的支持的上传格式如下 (在管理后台的”网站设置 / 全局设置”中可以进行修改):

webp|jpg|gif|png|jpeg|bmp

总之, 将允许上传的文件格式限制得越少越好.

删除不必要的文件

Z-BlogPHP 的安装程序在 zb_install 目录下, 安装完成后 zb_install 这个目录及其下的所有文件都可以直接删除以减少网站的受攻击面 (该删除操作不会影响网站的正常登陆和访问).

Solve the Problem of ownCloud: ‘No Keychain Service Available’ on Ubuntu 18.04

Operating Environment

Operating system: Ubuntu 18.04.2 LTS
ownCloud client version: 2.4.1

Problem Description

I use ownCloud in Ubuntu and I add one account to it. But whenever I reboot Ubuntu, ownCloud will tell me: “No keychain service available” and I’ll be asked to enter the password once, as show in the figure 1:

Figure 1
Figure 1

Once I enter the password, ownCloud will work properly. But once I restart Ubuntu Linux, that happens again and again. Passwords should be stored, but it didn’t.

Solve the Problem

I found a solution on Github through Bing and that link is HERE. It seems that I am not the only one who has encountered this problem.

The solution is very simple, installing:

sudo apt install libgnome-keyring0

Then, reboot Ubuntu Linux, after you log in to the system, you will be asked to enter a password once, only once, whether you restart Ubuntu or not, you will not be asked to enter ownCloud’s password again unless you exit ownCloud account.

This problem has been solved.

在 Windows 10 上优化 CodeBlocks 外观 (主题 + 字体)

操作环境

操作系统: Windows 10 64 位 中文家庭版
CodeBlocks 版本: Code::Blocks 17.12

操作说明

CodeBlocks 是一款优秀的 IDE, 但是其默认的主题实在是不能满足我对一个代码编译器最基本的需求: 不使用亮色主题.
CodeBlocks 默认的主题是白色的, 如图 1:

图 1
图 1

在写代码时, 编辑器通常都是全屏显示的, 白色的主题看久了眼睛是受不了的. 不过, 好在 CodeBlocks 官网上提供了暗色的主题可供选择, 本文就介绍一下通过 CodeBlocks 官网提供的主题代码更换 CodeBlocks 主题以及修改 CodeBlocks 默认字体的方法.

继续阅读“在 Windows 10 上优化 CodeBlocks 外观 (主题 + 字体)”

在 Debian 7 Linux 中将 PHP 5.4 升级到 PHP 5.6

操作环境

root@IronMan:~# cat /etc/issue
Debian GNU/Linux 7 \n \l

操作步骤

指定软件源, 编辑:

vim /etc/apt/sources.list.d/dotdeb.list

写入如下内容:

deb http://packages.dotdeb.org wheezy-php56 all
deb-src http://packages.dotdeb.org wheezy-php56 all

将上述软件源加入 apt key:

wget http://www.dotdeb.org/dotdeb.gpg -O- | apt-key add -

更新可用包列表:

aptitude update

升级 PHP 版本:

aptitude install php5-cli php5-fpm

2017 年蓝桥杯 C 语言 B 组省赛第 2 题: 等差素数列

题目

标题:等差素数列

2,3,5,7,11,13,….是素数序列。
类似:7,37,67,97,127,157 这样完全由素数组成的等差数列,叫等差素数数列。
上边的数列公差为30,长度为6。

2004年,格林与华人陶哲轩合作证明了:存在任意长度的素数等差数列。
这是数论领域一项惊人的成果!

有这一理论为基础,请你借助手中的计算机,满怀信心地搜索:

长度为10的等差素数列,其公差最小值是多少?

注意:需要提交的是一个整数,不要填写任何多余的内容和说明文字。

题目分析

首先, 素数 (质数) 都是大于 1 的, 因此 1 不是素数, 这点在题目中也有说明.
本题的解题思路是这样的:
我们首先需要定义一个用于判断一个数是不是素数的函数, 下面这个函数就可以判断一个大于 2 的数是不是一个素数, 如果是素数就返回真:

bool f(int x){
    for(int i=2;i<x;i++){
        if(x%i==0){
            return 0;
        }
    }
    return 1;
}

这里需要注意的是, 如果把上面这个判断素数的函数写成下面这样是不对的, 因为如果一个数能够整除除了自身和 1 之外的其他数则可以断定该数不是素数, 但是如果一个数不能够整除除了自身和 1 之外的其他数中的一个, 是不能判断其是不是素数的, 错误的素数判断程序如下:

bool f(int x){
    for(int i=2;i<x;i++){
        if(x%!=0){
            return 1;
        }
    }
    return 0;
}

之后, 我们需要从素数的起始位置, 也就是数字 2 开始, 从小到大依次判断前 1 个数加 1 是不是素数, 如果不是素数且之前计算出来的数字个数小于 10, 那么第 2 个素数开始进行上面的逐个加 1 的操作, 直到第 99999 个数字 (这个数字不一定是素数, 如果不是素数不参加之前的运算) 为止, 如果不能以 1 为公差找到 10 个数字, 则之后还是从数字 2 开始逐个加 2 并判断是不是素数, 如果不是素数则从第二个素数开始逐个进行加 2 的操作, 直到第 99999 个数字为止, 如果不能以 2 为公差找到 10 个数字, 则之后从数字 2 开始进行加 3 的操作…直到第 99999 个数字为止.

如果计算之后找不到结果可以将上面提到的有关搜索范围适当扩大.

在具体的编程实现上有以下两种.

程序 1:

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

int a[99999];
bool f(int x){
    for(int i=2;i<x;i++){
        if(x%i==0){
            return 0;
        }
    }
    return 1;
}

int main(){

    for(int a=2;a<99999;a++){ //遍历首项
        for(int b=1;b<999;b++){ //遍历公差

//使用十层 if 判断寻找符合条件的数值, 能抵达第十层的说明满足条件, 输出之
            if(f(a)){
                if(f(a+b*1)){
                    if(f(a+b*2)){
                        if(f(a+b*3)){
                            if(f(a+b*4)){
                                if(f(a+b*5)){
                                    if(f(a+b*6)){
                                        if(f(a+b*7)){
                                            if(f(a+b*8)){
                                                if(f(a+b*9)){
                                                    cout<<b<<endl;
                                                }else{
                                                    continue;
                                                }
                                            }else{
                                                continue;
                                            }
                                        }else{
                                            continue;
                                        }
                                    }else{
                                        continue;
                                    }
                                }else{
                                    continue;
                                }
                            }else{
                                continue;
                            }
                        }else{
                            continue;
                        }
                    }else{
                        continue;
                    }
                }else{
                    continue;
                }
            }else{
                continue;
            }
        }
    }

    return 0;
}

程序 2:

#include<iostream>
#include<bits/stdc++.h>
#define MAX 999999
using namespace std;
int a[MAX];

bool f(int x){ //判断一个数字是不是素数的函数
    for(int i=2;i<x;i++){
        if(x%i==0){
            return 0;
            //如果能整除, 则表示这是一个非素数, 返回假 (0)
        }
    }
    return 1;
    //如果不能整除, 则表示这是一个素数, 返回真 (0)
}

int main(){

//遍历从 2 到 99999 之间的数字, 将其中的素数标记为 1
    for(int i=2;i<99999;i++){
        if(f(i)){
            a[i]=1;
        }
    }

    for(int delta=1;delta<999;delta++){
    //遍历从 1 到 999 之间的公差
        for(int i=2;i<99999;i++){
        //遍历数组 a[i]

            int step;
            for(step=0;step<10;step++){
            //限制只遍历数组 a[i] 中的 10 个数字

                //如果第 i+delta*step 个数字不是素数就退出
                if(a[i+delta*step]!=1){
                    break;
                }
            }

            //如果能完整的走完 10 步则表示找到了答案
            if(step==10){
                cout<<delta<<endl;
                return 0;
            }
        }
    }
    return 0;
}

本题的正确答案:
210

2016 年蓝桥杯 C 语言 B 组省赛第 3 题: 凑算式 (两种方法)

题目

凑算式

     B      DEF
A + --- + ------- = 10
​     C      GHI


(如果显示有问题,可以参见【图1.jpg】)


这个算式中A~I代表1~9的数字,不同的字母代表不同的数字。

比如:
6+8/3+952/714 就是一种解法,
5+3/1+972/486 是另一种解法。

这个算式一共有多少种解法?

注意:你提交应该是个整数,不要填写任何多余的内容或说明性文字。

图 1
图 1

题目分析

本题就是一个无重复全排列的问题, 在 C++ 中实现无重复全排列有以下两种主要方式:

  1. 通过 next_permutation() 函数实现全排列

next_permutation() 全排列函数只能对有序数列的数组进行全排列, 示例如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
    int a[3]={0,1,2};
    while(next_permutation(a,a+3)){
        cout<<a[0]<<a[1]<<a[2];
        cout<<endl;
    }
    return 0;
}
  1. 通过循环实现全排列

假设我们要对从 1 到 3 这个三个数字进行全排列, 那么我们可以使用如下的方式进行:

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

int main(){
    for(int a=1;a<=3;a++){
        for(int b=1;b<=3;b++){
            if(b!=a){
                for(int c=1;c<=3;c++){
                    if(c!=a&&c!=b){
                    cout<<a<<" "<<b<<" "<<c<<endl;
                    }
                }
            }
        }
    }
    return 0;
}

另外, 在本题中需要注意的是, 本题给出的算式是存在除法的, 涉及除法就不得不考虑精度的问题. 为了解决精度的问题, 一方面我们可以使用将整型强制转换成 double 型的方式, 另一方面也可以使用将除法转换成乘法的方式进行计算.

根据上面的分析, 我们可以有两种方法计算本题.

方法一: 使用 C++ 中的 next_permutation() 函数计算, 程序如下:

#include<iostream>
#include<bits/stdc++.h> //万能头文件, 包含所有函数
using namespace std;
int main(){
    int a[9]={1,2,3,4,5,6,7,8,9};
    int sum=0;
    while(next_permutation(a,a+9)){
    //对数组a的前9个元素进行全排列, 也就是对全部元素进行全排列

        double sum2=(double)a[0]+(double)a[1]/a[2]+double(a[3]*100+a[4]*10+a[5])/(a[6]*100+a[7]*10+a[8]);
        if(sum2==10.0){
            sum++;
        }
    }
    cout<<sum<<endl;;
    return 0;
}

方法二: 使用九层 for 循环计算, 程序如下:

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

int main(){
    int ans=0;
    for(int a=1;a<=9;a++){
        for(int b=1;b<=9;b++){
            if(b!=a){
                for(int c=1;c<=9;c++){
                    if(c!=a&&c!=b){
                        for(int d=1;d<=9;d++){
                            if(d!=a&&d!=b&&d!=c){
                                for(int e=1;e<=9;e++){
                                    if(e!=a&&e!=b&&e!=c&&e!=d){
                                        for(int f=1;f<=9;f++){
                                            if(f!=a&&f!=b&&f!=c&&f!=d&&f!=e){
                                                for(int g=1;g<=9;g++){
                                                    if(g!=a&&g!=b&&g!=c&&g!=d&&g!=e&&g!=f){
                                                        for(int h=1;h<=9;h++){
                                                            if(h!=a&&h!=b&&h!=c&&h!=d&&h!=e&&h!=f&&h!=g){
                                                                for(int i=1;i<=9;i++){
                                                                    if(i!=a&&i!=b&&i!=c&&i!=d&&i!=e&&i!=f&&i!=g&&i!=h){
                                                                        double sum2=(double)a+(double)b/c+double(d*100+e*10+f)/(g*100+h*10+i);
                                                                        if(sum2==10.0){
                                                                            ans++;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

2016 年蓝桥杯 C 语言 B 组省赛第 2 题: 生日蜡烛 (三种解法)

一、题目

生日蜡烛

某君从某年开始每年都举办一次生日party,并且每次都要吹熄与年龄相同根数的蜡烛。

现在算起来,他一共吹熄了236根蜡烛。

请问,他从多少岁开始过生日party的?

请填写他开始过生日party的年龄数。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

二、题目分析

这道题可以使用循环来解决. 根据实际情况, 首先假设”某君”的年龄没有超过100岁, 之后假设他从 0 岁开始过生日并吹蜡烛, 之后一直累加, 直到他吹蜡烛的总数为 236, 如果从 0 岁开始计算没有出现吹了 236 根蜡烛的情况, 那么就从 1 岁开始计算, 直到计算出吹了 236 根蜡烛的情况.

在具体的程序实现方式上有三种不同的解法.

第一种解法是, 第一层 for 循环用来遍历开始过生日的年龄, 第二层 for 循环用来遍历现在的年龄, 第三层 for 循环用来遍历从开始年龄到现在年龄之间吹的蜡烛的总数, 程序如下:

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

int main(){
    for(int start=1;start<100;start++){
    //遍历开始过生日的年龄

        for(int now=1;now<100;now++){
        //遍历现在的年龄

            int sum=0;
            if(start<=now){
            //现在的年龄需要大于过去的年龄

                for(int i=start;i<=now;i++){
                //累加计算蜡烛总数
                    sum=sum+i;//注意这里要累加的变量是 i 不是 start
                }
                if(sum==236){
                //如果蜡烛总数为 236 则输出结果
                cout<<"开始的时间:"<<endl;
                cout<<start<<endl;
                cout<<"现在的时间:"<<endl;
                cout<<now<<endl;
                }
            }
        }
    }
    return 0;
}

第二种解法是, 第一层 for 循环用来遍历开始过生日的年龄, 第二层 for 循环用来遍历下一次过生日时的年龄并进行累加, 程序如下:

#include<iostream>
#include<stdio.h>
using namespace std;
int main(){
    for(int i=0;i<200;i++){ //i 为开始年龄
        int sum=0;
        //sum 要调用它的循环外最近的地方定义并初始化

        for(int j=i;j<200;j++){
        //j 为下次过生日时的年龄

            sum=sum+j;
            //计算吹蜡烛的总数

            if(sum==236){
                cout<<"开始的年龄是:"<<i<<endl;
                cout<<"现在的年龄是:"<<j<<endl;
            }
        }
    }
    return 0;
}

第三种解法是, 利用等差数列的求和公式进行计算.

等差数列的前 n 项和:

$S_n$ $=$ $\frac{n(a_1+a_n)}{2}$

等差数列的第 $n$ 项值:

$a_n$ $=$ $a_1$ $+$ $(n-1)$ $d$

等比数列的前 $n$ 项和:

$S_n$ $=$ $\frac{a_1(1-q^n)}{1-q}$

等比数列的第 n 项值:

$a_n$ $=$ $a_1$ $q^{(n-1)} $

根据等差数列的数学原理, 我们可以编写出如下计算程序:

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

int main(){
    for(int i=1;i<=100;i++){
    //i 为数列的首项

        double sum=0;
        int a=0;
        int b=0;
        for(int j=1;j<=100;j++){
        //j 为数列递增的次数

            b=j+1;
            //目前数列有 b 项 (项数比递增的次数多 1)

            a=i+b-1;
            //计算本数列最后一项的值

            sum=b*(i+a)/2;
            //计算本数列的和

            /*每计算出一次 sum 的值都要立即进行一次判断
            判断语句不能放在本次循环的外面
            因为一个循环会遍历所有的数值之后才退出
            如果把判断语句放在循环的外面
            则不能使每次循环得出的数值都被判断*/
            if(sum==236){
            cout<<"开始过生日的年龄为:"<<i<<endl;
        }
        }

    }
    return 0;
}

在使用循环遍历的时候, 如果不能计算出我们想要的结果, 这个时候可以把循环的次数改小一些, 之后进行单步调试, 看看是不是和我们手算出来的结果一致.

2016 年蓝桥杯 C 语言 B 组省赛第 1 题: 煤球数目

题目

煤球数目

有一堆煤球,堆成三角棱锥形。具体:
第一层放1个,
第二层3个(排列成三角形),
第三层6个(排列成三角形),
第四层10个(排列成三角形),
….
如果一共有100层,共有多少个煤球?

请填表示煤球总数目的数字。
注意:你提交的应该是一个整数,不要填写任何多余的内容或说明性文字。

题目分析

本题是一个找规律的问题, 找到的规律如下:

第1层: 0+1=1
第2层: 1+2=3
第3层: 3+3=6
第4层: 6+4=10
……

可以看到, 规律就是上一层的煤球个数加上本层的层数就可以得到本层的煤球个数.

之后用编程实现, 代码如下:

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int main(){
    int sum=0;
    int sum2=0;
    for(int i=1;i<=100;i++){
        sum=sum+i;
        sum2=sum2+sum;
    }
    cout<<sum2<<endl;
    return 0;
}

本题正确答案:
171700

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


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

豫 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