]]>
]]>
当你借鉴本文后,有了自己的一套整理文件的方案,于是你可以借助其它应用帮助你更好的整理文件,例如:在 macOS 上使用 「Hazel」和「Automator」达到自动整理的目的,极大减少手动操作的时间。要注意的是:这些工具只能减少你花费的时间,提高效率,而不能有效地替你完成期望行为,关键的内容仍然需要你自己整理!
本文借鉴了一些出色的文章,这里一一列出:
总体来说,文件的分类大致分为以下几个步骤:
在 macOS 、 Windows 以及 Linux 下,总有一个用户目录,其包含了「Downloads」、「Documents」、「Music」、「Movies」、「Pictures」和 「Desktop」。本文的整理规则按照这些目录展开,Windows 的用户可以此为参照。事实上,Windows 的用户目录也包含了这些文件夹,然而由于历史原因,人们大多习惯于把文件储存于其它地方,本文暂不涉及 Windows 下的文件夹管理的具体操作流程。
这一个步骤是一切整理的开端,其启发来源于「Get Things Done」的第一个步骤。
收集文件,文件的来源有很多,比如网上下载、QQ或微信的接收、本地新建、应用自我生成等。但总的来收可以分为三类:外部来源文件、本地可移动文件、本地不可移动文件。
在 macOS 上,对这三类文件给出处理方式:
注:通常情况下,我们情不自禁地把文件放到桌面,那么我们可以在桌面放一个「Inbox」的快捷方式,方便得收集文件。
文件的处理,其根本是对它们进行分类。科学的分类方案是对文件进行前端控制的前提和关键,亦是组织自己知识结构的必要过程。大数据时代,建立一个自己的电子实体分类、信息分类、处置、整理于一体的健全方案已经是每个人所需要掌握的必然技能。
电子文件分类是一个由总到分、由抽象到具体地逐级揭示文件的,以规范的类目名称、明确的内涵和广泛的使用性实现电子文件的分类。基于此,分类采用的是多层次树形结构,具体来说,以「用户目录」为根目录,以「文件职能」为基础(一级目录),「活动层级」为路径(多层目录),「事务目标」为终点(最后一层目录),重要的文档则存放在「事务目标」的目录中,即形成「活动层级」->「活动层级」->「事务目标」的结构。
如何确定「文件职能」、「活动层级」、「事务目标」呢?
至于第二点,我为什么不把文件简单得命名为「少数派.文件整理指导办法.2018-08-13.[v0.2].md」,然后建立一个名叫「Draft」的活动层级子目录并把文件存放到这下面;或者命名为「Draft.文件整理指导办法.2018-08-13.[v0.2].md」,然后建立一个「少数派」的活动层级子目录并把文件存到这下面,我会在之后给出原因。
另外,我们注意到文件本身由「文件内容」和「文件信息」组成。其中,文件信息包含有:文件名、文件类型、文件大小、文件修改时间等信息。文件内容及文件之间的抽象逻辑关系是确定「活动层级」文件夹的关键;文件信息所包含的「文件修改时间」是版本控制的基础;剩余的文件信息和文件内容的主要目的是文件命名和标签设计的关键。这里的标签可以是系统自带的一个功能,也可以是文件名中的一个关键字,如「Draft」,具体解释就是:标签是对同一文件夹下聚合同类文件的一种手段。
在实施文件整理时有这样几个原则:
以上第 3 个规则解释了我为什么不将「少数派.通用文件整理办法.2018-08-13.[v0.2].Draft.md」这个文件更加细化活动层级的原因,即大量同族文件不作细分,全都归纳在一个大文件夹下。
当文件确定了「事务目标」后重要的就是文件的重命名了。文件的命名,不单单要指出文件的内容,也需要含有一定的信息以聚合相类似的文件,或者,你可以添加一些必要的标签以表示「状态」和「特点」和版本号以控制版本。
现在,你会发现一个文件名需要包含的内容有:文件的标题、文件信息、文件的多个标签、版本号、扩展名。注意的是,由于文件系统前端展示的特点——按字典序排序,你需要把你越需要聚合的特点越放在文件名的前面。
如,我希望把同是「少数派」的文章按照状态依次列出,且相同标题的文章按照创建时间递增,至于版本也需要依次增大。那么,我可以按照如下形式命名规则命名文件:「类别.状态.标题.创建时间.版本.扩展名」。
又或者,我希望把「学校」事务目标文件夹下的文件,首先把同一个项目类的文件聚合,其次按照文件的标题归纳在一起,接着按照创建时间和版本递增排序。那么,有如下规则:「项目类.文件标题.创建时间.版本.扩展名」。
在实际情况中,我们总会高频率地访问某些文件夹。于是,我们可以在对应的「文件职能」文件夹或者「Documents」文件夹下建立一个「Working On」的文件夹用来存这些高访问频率的文件或文件夹的超链接。然后,你可以把它放到桌面,更加便捷得访问。
至此,简单的整理方法以及写完了。希望大家能够按照自己的文件特点归纳出合适的文件整理办法。
]]>什么是 Detecion ?与 Detection 相关的 Classification 和 Segmentation 呢?
看上图[1],子图 (a) 的情况就是Classification,对于输入的图片只给出了图里存在的物品类别;子图(b)则相对于 (a) 来说,不仅给出了类别还给出了这个物品的位置,这就是 Object Detection;子图 (c)、(d) 则是 Segmentaion 的两个方向,这里不做细说。
知道了 Detection 是什么后,我们来说说 CV 领域关于 Detection 的发展方向。首先,Detection 的主要问题是:图里有什么(Classification),物体具体在图的哪里且大小如何(Localization)。
Detection 有两个方向解决上面提到的两个问题:
这个方法使用 Regression 的思路。普通 Classification 能获取物体的类别但是不知道位置,那么既然能知道类那能不能也知道位置呢?于是基于普通的 Classification 在某个 Feature Map 的基础上兵分两路,一路是 Classification(原来的路径),另一路则利用 Regression 通过 Feature Map 获得 Localization 需要的东西——坐标。
其简单结构可以看下图。
这种办法与 One-Stage 不同在,它先需要先产生多个 Localization 需要的坐标,然后再逐一判断每个坐标对应的候选框哪个“最好”。
当然,产生的坐标方式有很多,最笨的办法是暴力枚举。好的办法当然有,比如本文讲的 Faster R-CNN 的鼻祖——RCNN。
比暴力好的办法当然有,R-CNN 就算一个。
R-CNN 的具体过程:
利用图片本身来获取一些物体可能存在的候选区域(Region Proposal)其中,候选区域使用 Selective Search[3] 算法。
注意:通过 Selective Search 获取候选区域后会有一个小问题,就是候选区域的大小都是不同的,于是需要利用缩放将所有候选区域统一到一个大小(论文中为227)。这样,就可以得到统一的、可以放入 CNN 的候选区域了。
对于这些统一的候选区域,通过 CNN 做一次 forward 计算,就可以得到 Feature Map 了,这样可以继续后面的操作。
同时,这些候选区域根据 Ground Truth 通过 NMS 方法过滤大部分无用的候选区域,且为有用的候选区域确定其对应的类别标签。
对于这些 Feature Map 以及其对应的类别标签,我们把他们送入各个类的类别分类器(论文中为 SVM,且有20个分类器)进行训练。当测试时,遍历这些 SVM 来判断其是否属于该类。由于负样本很多,使用hard negative mining 方法。
对于这些 Feature Map 以及其对应的 Ground Truth,再利用 Regression 进行训练。测试时,获取候选框的修正量,以便获取更为准确的候选框。
但是,R-CNN 有两个明显的问题:
为了解决第上面两个问题,提出了 Fast R-CNN。
讲 Fast R-CNN 之前要先讲讲 Fast RCNN 借鉴的一个重要方法 SPPNet[5] 。
SPPNet 的提出主要是解决 R-CNN 的第一个问题,即输入固定的问题。
研究 “图片 --> 卷积层 --> 全连接层” 这个结构,你会发现 “图片 --> 卷积” 这个结构尺寸匹配没有问题,有问题在于之后的 “卷积层 --> 全连接层” 这个结构。
那么,解决这个问题有两个方法:
SPPNet 选择第二种办法,通过 SPP (Spatial Pyramid Pooling) 这个结构解决了输入固定的问题。
具体结构如下图
框内的结构就是 SPP 。可以明显看出,对于 SPP 的输入(卷积层的结果),使用3种尺度的 pooling 层来获得固定的输出,具体来说(以中间为例),对于卷积层的结果先经过第一个pooling层,获得 的固定输出(例子如下图),同理可以获得 和 的结果,于是将其展开可以获得估计为 21 的输出()。
有了 SPPNet 的结构的基础,才有了 Fast R-CNN 的提出。
与 R-CNN 相比,Fast R-CNN 有两点改进:
具体过程:
对于这个 ROI Pooling,其实就是 SPP 的变体,其不再是多个 Pooling 层,而是只有一个 Pooling 层。
结构可见下图:
至此,Fast R-CNN 的训练过程结束。
A Review on Deep Learning Techniques Applied to Semantic Segmentation,Fig 1 ↩︎
R. Girshick, J. Donahue, T. Darrell, and J. Malik, “Rich feature hierarchies for accurate object detection and semantic segmentation,” in IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2014. ↩︎
J. Uijlings, K. van de Sande, T. Gevers, and A. Smeulders.Selective search for object recognition. IJCV, 2013. ↩︎
R. Girshick, “Fast R-CNN,” in IEEE International Conference on Computer Vision (ICCV), 2015. ↩︎
K. He, X. Zhang, S. Ren, and J. Sun, “Spatial pyramid pooling in deep convolutional networks for visual recognition,” inEuropean Conference on Computer Vision (ECCV), 2014. ↩︎
[解]:
令 ,
证单调性。 为奇函数,则考虑 。因为 ,且 单调递减,所以 。
则明显有
即, 单调递减。
证有界。 天生有界,知 。
故 存在。设 ,令 , 由 ,得 , 解得 。
同理,当 时, 。则极限为 0 。
]]>]]>
[分析]:
[注]:
高阶无穷小可舍去。原因:0.01 + 0.000001 ~ 0.01 ,因此,可以舍去那个极小的值。
Normalization 就是把数据进行前处理,从而使数值都落入到统一的数值范围,从而在建模过程中,各个特征量没差别对待。Normalization 一般是把数据限定在需要的范围,比如一般都是 ,从而消除了数据量纲对建模的影响。并且对基于 gradient descent 的算法友好,能加快训练速度,促进算法的收敛。
注:Standardization 是 Normalization 的一种特殊情况,它对数据进行正态化处理,使数据的平均值为1,方差为0。
Regularization 是在 cost function 里面加惩罚项,增加建模的模糊性,从而把捕捉到的趋势从局部细微趋势,调整到整体大概趋势。虽然一定程度上的放宽了建模要求,但是能有效防止过拟合(over-fitting)的问题。
Normalization 的手段很多,主要有:
Regularization 主要是处理过拟合的情况,它对某些特征值进行处罚,简单来说就是降低重要性。
以 Liner Regression 为例,在算法中,我们的步骤为:
cost function:
求导完后有梯度下降迭代式,其中 始终为 1 ,不参与迭代:
]]>
决策树本事上是一棵树,树的根节点包含了样本全集,从根出发到叶节点的路径对应了一个判定测试序列,而每个内部节点包含的样本集合则根据相应属性的测试结果而划分到子节点中。
注:决策树在 Coursera 和 Stanford 的公开课中并未提到,相关知识完全通过《机器学习》和互联网获得。
]]>决策树本事上是一棵树,树的根节点包含了样本全集,从根出发到叶节点的路径对应了一个判定测试序列,而每个内部节点包含的样本集合则根据相应属性的测试结果而划分到子节点中。
注:决策树在 Coursera 和 Stanford 的公开课中并未提到,相关知识完全通过《机器学习》和互联网获得。
讲基本过程之前,先来搞懂“先验概率”、“后验概率”、“似然函数”。
先验概率:就是常识、经验或者统计方法所透露出的“因”的概率,(原因),就是先验概率。
后验概率:这种先知道结果,然后由结果估计原因的概率分布,(原因|结果),就是后验概率。
似然函数:这种先确定原因,根据原因来估计结果的固有性质的可能性(likelihood),是对固有性质的拟合程度,(结果|原因),就是似然估计。
于是有了贝叶斯公式:
:观察得到的数据(结果)
:决定数据分布的参数(原因)
:后验概率(posterior probability)
:先验概率(prior probability)
:似然估计(likelihood)
:evidence
懂了上述概念,接着讲讲基本流程,训练集 ; 属性集 :
函数 TreeGenerate(D,A)
生成节点node:
if D 中的样本全属于同一类别 C then
将node标记为C类叶子节点;return
end if
if A = ϕ OR D 中样本在 A 上取值相同 then
将 node 标记为叶节点,其类别标记为 D 中样本数最多的类;return
end if
从A中选则最优划分属性a*;
for a* 的每一个值a*(v) do
为node生成一个分支;令Dv表示D中在a*上取值为a*(v)的样本子集;
if Dv 为空 then
将分支节点标记为叶节点,其类别标记为D中样本最多的类;return
else
以Tree(Dv,A\{a*})为分支节点
end if
end for
决策数的生成是一个递归的过程,在决策树基本算法中有三种情况会导致递归返回:
上面的基本过程的的本质是构建一课树,核心是划分最优属性 ,且希望决策树的分支节点所包含的样本尽可能的属于同一类别,即节点的“纯度”尽可能高。
划分选择有多种算法,如:ID3、C4.5。
在一开始 我们提一下熵。熵的概念最早起源于物理学,用于度量一个热力学系统的无序程度。在信息论里面,熵是对不确定性的测量。但是在信息世界,熵越高,则能传输越多的信息,熵越低,则意味着传输的信息越少。
信息熵(information entropy)是度量样本集合纯度最常用的一种指标,假定当前样本集合 中第 类样本所占的比例为 ,则 的信息熵定义为:
Ent(D)的值越小,则D的纯度越高。
为了找出“纯度最大”的属性,我们引入“信息增熵”,简单来说就是“信息熵”与“条件熵”的差值,即信息增益代表了在一个条件下,信息复杂度(不确定性)减少的程度;表明了此条件的重要性。再给出“信息增熵”的定义:
所以:有了
为了解决信息增熵准则对可取数目较多的属性有所偏好,著名的 C4.5 决策树算法 [Quinlan,1993] 使用“增益率” (gain ration)来选择最优划分属性。
增益率定义为:
其中:
IV(a) 称为属性 a 的固有值,属性 a 的可能取值数目越多,则 IV(a) 的值通常会越大。增益率准则对取值数目较少的属性有所偏好。
因此,使用启发式:先从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的。
CART树(Classification and Regression Tree)使用基尼指数来选择属性的划分,通过基尼值来度量数据集的纯度
基尼值:
反映了从数据集 中取出两个样本,不为同一种类的概率,因此 Gini(D) 越小,数据集的纯度越高。
转化成与“信息增熵”相似的定义:
于是最优划分属性为:
预剪枝是在决策树生成的过程中,对每个结点在划分前先进行预估,若当前结点的划分不能使决策树泛化性能提升,则停止划分并将当前结点标记为叶节点。
即对比当前验证集精度与生成子决策树后的精度
后剪枝是先从训练集中生成一颗完整的决策树,然后自底向上的对非叶子结点进行考察,若将改结点对应的子树替换为叶子结点能提高泛化能力,则进行替换。
即,对每一分支,对比当前精度与去掉决策子树的精度。
后剪枝决策树比预剪枝决策树保留了更多的分支,一般情况下,后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝决策树,但后剪枝过程是生成完全决策树之后进行的,并且要自底向上地对书中的所有非叶子节点进行逐一考察,因此其训练时间开销比末剪纸决策树和预剪枝决策树都要大得多。
由于连续属性的可能取值不再有限,因此不能直接根据连续属性的可能取值进行划分。我们可以使用离散化技术对其进行处理。
二分法:
给定样本集 ,和连续属性 , 在 上出现了 个不同的取值,
将其排序 ,然后基于划分点 将样本划分为 和 , 包括了在属性 上取值小于 的样本, 反之。即
通常我们取区间 的中位点作为候选划分点。
然后有:
现实任务中常常会遇到不完整的样本,也就是样本中的某些属性值缺失的情况,最简单的方法是直接去除缺失的数据,但是这是对数据信息的极大浪费,我们可以考虑下利用有缺失属性值的样本来进行学习。
需解决的问题:
给定训练集 ,属性集 ,令 表示 中在属性 上没有缺失值的样本子集。
对问题一:可依据 来判断属性的优劣。假定属性 有 个可取的值 ,令 表示 中在属性 上的取值为 的样本集, 表示 中属于第 类的样本子集。
显然有 $ \tilde{D} = \bigcup^{|\mathcal{Y}|}_{k=1}\tilde{D}_k,\tilde{D} = \bigcup^{|V|}_{v=1}\tilde{D} ^v$ 。
假定给每个样本一个权重 ,并定义:
:在属性 中 ,无缺失值所占样本比例;
:无缺失值样本中第 类所占的比例;
:无缺失值样本中在属性 上的取值 的样本所占比例
显然有, 。
于是可以推广信息增益的计算式为:
其中:
对问题二,样本 在属性 上的取值缺失,则将 划分到所有叶子结点中,将权值变为 意思将同一个样本以不同的概率划入到同的子结点中去。
]]>在 Liner Regression 中 是一个连续值,那么当我们解决一个二分类问题时 则是一个离散值,只有两个取值( 或 ),这时可以通过广义线性模型来解决。通过广义线性模型,我们找到一个单调可微函数将分类任务的标记 与线性模型的预测值联系起来。最后我们找到 Logistic Function 来作为线性模型。
]]>在 Liner Regression 中 是一个连续值,那么当我们解决一个二分类问题时 则是一个离散值,只有两个取值( 或 ),这时可以通过广义线性模型来解决。通过广义线性模型,我们找到一个单调可微函数将分类任务的标记 与线性模型的预测值联系起来。最后我们找到 Logistic Function 来作为线性模型。
在笔记中,我先把广义线性模型梳理一遍,这有助于我更好得学习机器学习。
当然如果仅仅是为了学习 Logistic Function 那么可以先看看后面的内容,之后再看这部分内容。
在回归学习中,我们的函数都类似于 或者在之后讲的二分类函数 ,这里的 和 都分别是 和 的某种函数( 与分布无关 )。其实,有一种更广泛的模型,这两种模型都是它的特例,这种更广泛的模型叫做广义线性模型。
在广义线性模型中(GLM), 对于每个独立参数的 ,假设通过一个指数族产生。这就是说,对于均值 , 和独立变量 ,有:
是 的期望; 是一个线性估计; 是链接函数。
关于广义线性模型更多的知识请前往 Wikipedia 。
在学习广义线性模型之前,我们要先定义一下指数族分布(exponential family distributions)。如果一个分布能用下面的方式来写出来,我们就说这类分布属于指数族:
上面的式子中,
:该分布的自然参数(natural parameter,也叫典范参数 canonical parameter);
:充分统计量(sufficient statistic),我们目前用的这些分布中通常 ;
:一个对数分割函数(log partition function);
$e^{−a(\eta)} $ :这个量本质上扮演了归一化常数(normalization constant)的角色,也就是确保分布的 的总和等于1。
对 给定的 , 和 就定义了一个以 为参数的分布族(family,或者叫集 set);通过改变 ,我们就能得到这个分布族中的不同分布。
现在咱们看到的伯努利(Bernoulli)分布和高斯(Gaussian)分布就都属于指数分布族。伯努利分布的均值是 ,也写作 ,确定的分布是 ,因此有 。这时候只要修改 ,就能得到一系列不同均值的伯努利分布了。现在我们展示的通过修改 ,而得到的这种伯努利分布,就属于指数分布族;也就是说,只要给定一组 , 和 ,就可以用上面的等式来确定一组特定的伯努利分布了。
伯努利分布通过广义线性模型可以这样写:
因此,给出了自然参数(natural parameter) 。 有趣的是,如果我们翻转这个定义,通过 表示 就会得到 。这正好就是我们之后在 Logistic Function 中会见到的 S 型函数(sigmoid function)! 在我们把逻辑回归作为一种广义线性模型(GLM)的时候还会遇到伯努利分布以如下情况表示。
接下来就看看高斯分布。在推导线性回归的时候, 的值对我们最终选择的 $\theta $ 和 都没有影响。所以我们可以给 取一个任意值。为了简化推导过程,就令 。然后就有了下面的等式:
注:如果我们把 作为一个变量,高斯分布就也可以表达成指数分布的形式,其中 就是一个同时依赖 和 的二维向量。然而,对于广义线性模型GLMs方面的用途,参数 就也可以看成是对指数分布族的更泛化的定义: 。这里面的 叫做分散度参数(dispersion parameter),对于高斯分布, ;不过上文中已经进行了简化,所以就不对各种需要考虑的情况进行更为泛化的定义了。
从最基本开始,我们不使用广义线性模型,对于二分类问题,输出标记 $\boldsymbol{y} \in {0,1} $ ,于是我们使用线性回归最基本的模型 来预测 ,即我们将 转化为 值。最理想的是 “单位越阶函数”(unit-step function):
但是,很明显,此函数不是很完美,于是,我们找到了一个“替代函数”来近似这个“单位跃阶函数”,并希望它单调可微,对数几率函数(Logistic Function)便满足这样一个条件:
由图你能直观得看到,当 的时候 趋向于 ,而当 时 趋向于 。
其实 也是 ,且,像最开始一样,我们规定 ,于是有:
现在我们看看 的特性:
接着我们通过对 进行假设,得到:
然后就能写出似然函数:
又与之前一样写出 的对数函数 以方便计算。
于是有 cost function:
然后目标就是:
我们从最开始得到的假设函数讲起。如何得到它呢?
我们首先假设:
于是,更简单的写法就是:
假设 个训练样本都是各自独立的,那么就可以按如下的方式来写带参数的似然函数:
取对数更容易计算:
极大似然函数中,为了求得最优的 ,就是让 尽可能得大,于是在 cost function 中,我们加入系数 ,于是得到了:
其实,可以直接对 做梯度上升求得 。
按照向量的形式,我们对 的更新可以写成:
找到最优的第一步是对 求导,我们从一个样本开始:
其中,用到了上面提到的 的特性,即 ,然后梯度上升就简单写为:
然后,再扩展为一个训练集:
有趣的是,这个式子正好与线性回归看上去一样,但是这实际上并不相同,原因是,我们对于 的定义不同。但为什么相似呢?深层次的原因在于 广义线性模型 。
下面这个方法更好,但是数学难度较高,其基本方法是“求方程零点的牛顿法”。
具体讲讲。假如我们有一个从实数到实数映射的函数 ,然后要找到一个 ,来满足 ,其中是一个实数。牛顿法就是对 进行如下的更新:
对这个公式的简单解释是:通过一条逼近曲线的直线(切线)不断迭代来找到零点。
对于上述的图,在 A 的切线方程为: 。
为了求下一个迭代点 B ,则有 ,
即:
用这个办法,我们求解 中 的最优取值,即算 的解,即可以通过以下迭代式求得:
对于向量 的求解,我们扩展牛顿法到多维的情况,叫做牛顿-拉普森法(Newton-Raphson method),如下:
其中 是 对 的偏导。 是一个 矩阵(考虑 的话是 ),也可叫做 Hessian,具体定义为:
注意,当 小的时候牛顿法的速度明显更快,但是当 较大时,由于需要处理 Hession 矩阵,时间开销急剧增加。
设想你要构建一个模型,来估计在给定的某个小时内来到你商店的顾客人数(或者是你的网站的页面访问次数),基于某些确定的特征 ,例如商店的促销、最近的广告、天气、今天周几啊等等。我们已经知道泊松分布(Poisson distribution)通常能适合用来对访客数目进行建模。知道了这个之后,怎么来建立一个模型来解决咱们这个具体问题呢?非常幸运的是,泊松分布是属于指数分布族的一个分部,所以我们可以使用一个广义线性模型(Generalized Linear Model,缩写为 ExpoFamilyGLM)。
对刚刚这类问题如何构建广义线性模型呢?
对于这类问题,我们希望通过一个 的函数来预测 的值。为了构建出模型,我们先给出3个假设:
普通最小二乘其实是广义线性模型的一个特例,其中 是连续的,通过 给出的 服从高斯分布 ,经过上面的学习我们有:
第一行的等式是基于假设2;第二个等式是基于定理当 ,则 y 的期望就是 μ;第三个等式是基于假设1,以及之前我们此前将高斯分布写成指数族分布的时候推导出来的性质 ;第四个等式就是基于假设3。
二分类问题 ,可以通过伯努利分布(Bernoulli distribution)来对给定 的 进行建模。
伯努利分布,有 ,和在 $ y|x;\theta \sim \rm{Bernoulli}(\phi)$ 下有 。
则有:
这就是为什么在 Logistic Function 中我们用 做假设,即,一旦我们假设给定 的 的分布是伯努利分布,那么根据广义线性模型和指数分布族的定义,就会得出这个式子。
注:一个自然参数 的函数 ,,这个函数叫做规范响应函数(canonical response function),它的反函数 叫做规范链接函数(canonical link function)。因此,对于高斯分布来说,它的规范响应函数正好就是识别函数(identify function);而对于伯努利分布来说,它的规范响应函数则是 logistic function。
对于多分类问题,有 ,通过多项式分布(multinomial distribution) 建模。
把多项式推出一个广义线性模型,首先把多项式分布用指数分布族进行描述。
我们给出 个参数 ,对应各自的输出值的概率,由于 ,所以,有 。注意, 其实是 。现在该出 :
与之前不同,不再有$ T(y) = yT(y)$现在是一个 维的向量,而不是一个实数了。向量 中的第 i 个元素写成 。
给出一个记号:指示函数(indicator function),即 。如果参数为真,则等于1;反之则等于0。
所以我们可以把 和 的关系写成 $ (T(y))_i = 1{y = i}$。
现在把多项式写出指数分布族:
\begin{aligned} p(y;\theta) &= \phi^{1\\{y=1\\}}\_{1} \phi^{1\\{y=2\\}}\_{2} \cdots \phi^{1\\{y=k\\}}\_{k}\\\\ &= \phi^{1\\{y=1\\}}\_{1} \phi^{1\\{y=2\\}}\_{2} \cdots \phi^{1-\sum^{k-1}\_{i=1}1\\{y=i\\}}\_{k}\\\\ &= \phi^{(T(y))\_1}\_{1} \phi^{(T(y))\_2}\_{2} \cdots \phi^{1-\sum^{k-1}\_{i=1}(T(y))\_i}\_{k}\\\\ &=\rm{exp}\left((T(y))\_1\log{(\phi\_1)}+(T(y))\_2\log{(\phi\_2)} + \cdots + \left(1-\sum^{k-1}\_{i=1}(T(y))\_i\right)\log{(\phi\_k)}\right)\\\\ &=\rm{exp}\left((T(y))\_1\log{(\phi\_1/\phi\_k)}+(T(y))\_2\log{(\phi\_2/\phi\_k)} + \cdots + (T(y))\_{k-1}\log{(\phi\_{k-1}/\phi\_k)} +\log{(\phi\_k)}\right)\\\\ &=b(y)\rm{exp}(\eta^T\it{T}\rm{}\,(y)-a(\eta)) \end{aligned}
其中:
于是对于每一个 有链接函数:
为了简单计算,我们给出定义 。且对链接函数取反函数然后推导出响应函数,就得到了下面的等式:
这样得到 ,然后我们我们回代入 ,
得到相应函数:
上面这个函数从 映射到了 ,称为 Softmax函数。通过假设3,我们有了 $\eta_i =\theta_i^Tx $ ,其中的 就是参数了。我们这里还是定义 ,这样就有 ,与上文相呼应。
因此,我们有了模型:
于是,我们的假设函数是:
然后,对于一个训练集来说,我们为了求得 ,写出似然函数的对数:
然后我们可以通过梯度上升法或者牛顿法为求:
]]>
在统计学中,线性回归(Linear Regression)是利用称为线性回归方程的最小平方函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。
机器学习中,我们通过线性回归得到的模型对其它的输入值预测出相对应的输出值。上述的模型我们称它为 假设,函数表达为
在统计学中,线性回归(Linear Regression)是利用称为线性回归方程的最小平方函数对一个或多个自变量和因变量之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。
机器学习中,我们通过线性回归得到的模型对其它的输入值预测出相对应的输出值。上述的模型我们称它为 假设,函数表达为
现在,我们有 个样本 ,对于每一个样本 ,一共有 个特征值,为 。有 个系数(模型参数)。
则我们的预测函数为:
为了简单化函数的表示,我们规定 ,因此函数中的的参数 就是参数 。
上面提到的,它的的作用会在以后的笔记中详细解释。
在计算机中,为了简便计算,我们将数据向量化(vectorize):
在周志华的《机器学习》一书中,则将,表示为:
其实两个函数是一样的,因为有。但是在计算机中,第一种向量表示比较方便储存数据,我的笔记也就这么记了。
为了使估计值接近于,我们定义cost function :
有了cost function,我们的目标就是:
基于均方误差最小化来进行模型求解的方法称为“最小二乘法”。——周志华《机器学习》
上述的 cost function 就是基于了均方误差。最小二乘法就是试图找到一条“线”,使所有样本到直线的欧式距离之和最小。
求解 的方法有两个,一个是梯度下降(gradient descent),一个是通过求导得出极值。
首先,梯度下降是通过不断更新 的值,而找到最优的情况。对于一个凸函数来说,是让当前的 $\theta $ 往函数下降最快的方向进行移动,以此更新 $\theta $ ,而更新的量,就是函数的导数了。所以,对于某一特征系数有更新公式:
我们从一个样本来看梯度下降,首先对 求 的偏导:
于是,更新公式可写为:
对于一个训练集,我们就得到:
The "Normal Equation" is a method of finding the optimum theta without iteration.
首先,我们定义一下 , 是一个 的矩阵,事实上,考虑截距(),矩阵应该为 ,
我们有:
然后有 :
于是有:
因此,对 求 的偏导:
当对 的偏导为 时,可得极值,得:
得到最优 解 :
在对数据进行概率假设的基础上,最小二乘回归得到的 和最大似然法估计的 是一致的。所以这是一系列的假设,其前提是认为最小二乘回归(least-squares regression)能够被判定为一种非常自然的方法,这种方法正好就进行了最大似然估计(maximum likelihood estimation)。
首先假设目标变量与输入变量存在以下等量关系:
上式的 是误差项,用于存放由于建模所忽略的变量导致的效果 (比如可能某些特征对于房价的影响很明显,但我们做回归的时候忽略掉了)或者随机的噪音信息(random noise)。进一步假设 是独立同分布的 (IID ,independently and identically distributed) ,服从高斯分布(Gaussian distribution),其平均值为 ,方差(variance)为 。这样就可以把这个假设写成 。然后 的密度函数就是:
这意味着存在下面的等量关系:
上式为,在 取某个固定值的情况下,这个等式表达为,在 的情况下发生 的概率, 通常可以看做是一个 的函数。当我们要把它当做 的函数的时候,就称它为似然函数(likelihood function)在整个数据集下有:
结合之前对 的独立性假设(这里对 以及给定的 也都做同样假设),就可以把上面这个等式改写成下面的形式:
最大似然法(maximum likelihood)告诉我们要选择能让数据的似然函数尽可能大的 。也就是说,找到 能够让函数 取到最大值。
为了找到 的最大值,我们不能直接使用 ,而要使用严格递增的 的函数求最大值。使用对数函数来找对数函数 的最大值是一种方法,而且求导来说就简单了一些:
对于上式 ,由于 , 值不变,那么 取最大,即求下面的式子最小:
证毕。
多项式回归可以用来拟合二次、三次、高次模型,通过使用 等进行拟合。
这样便将高阶方程模型转换成线性回归模型。这也算是 特征缩放(Features Scaling) 的一种。
]]>:一个样本
:样本的某一特征
:数据集合
:样本空间
:“标记空间” 或 “输出空间”
:特征数
:样本数
:概率分布
:学习率
:上某一特征下的均值
:上某一特征下的标准差
:假设空间
: 的估计量
]]>]]>
解答:
]]>
,
就是在沿x轴的斜率,就是沿y轴的斜率。
为沿x轴的增量,为沿y轴的增量,为沿z轴的增量。
则:
]]>
辗转相除法
筛法:
pku2689 Prime Distance(很好的一个应用)
反素数:
zoj2562 More Divisors
素数判断,整数分解:
pku1811 Prime Test
pku2429 GCD & LCM Inverse
欧拉函数:
pku1284 Primitive Roots (很水)
pku2407 Relatives (很水)
pku2773 Happy 2006
pku2478 Farey Sequence (快速求欧拉函数)
pku3090 Visible Lattice Points (法雷级数)
推荐:(欧拉函数,费马小定理)
pku3358 Period of an Infinite Binary Expansion
整数分解
pku2992 Divisors
fzu1753 Another Easy Problem
hit2813 Garden visiting
pku3101 Astronomy (分数的最小公倍数)
简单题:
pku1006 Biorhythms
pku1061 青蛙的约会
pku2891 Strange Way to Express Integers
pku2115 C Looooops
pku2142 The Balance
*强烈推荐:
pku3708 Recurrent Function (经典)
pku3243 Clever Y
pku2417 Discrete Loggin
简单题:
pku1222 EXTENDED LIGHTS OUT
pku1681 Painter's Problem
pku1830 开关问题
推荐:
pku2947 Widget Factory
pku2065 SETI
强烈推荐:
pku1753 Flip Game
pku3185 The Water Bowls
变态题:
pku1487 Single-Player Games
简单:
ural1057 Amount of degrees
spoj1182 Sorted bit squence
hdu3271 SNIBB
较难:
spoj2319 Sequence
sgu390 Tickets
鸽巢原理:
pku2365 Find a multiple
pku3370 Halloween treats
容斥原理:
hdu1695 GCD
hdu2461 Rectangles
简单题:
pku1850 Code
pku1150 The Last Non-zero Digit
pku1715 Hexadecimal Numbers
pku2282 The Counting Problem
pku3286 How many 0's?
推荐:
pku3252 Round Numbers
计数序列:
pku1430 Binary Stirling Numbers
pku2515 Birthday Cake
pku1707 Sum of powers
简单题:(应该理解概念就可以了)
pku3270 Cow Sorting
pku1026 Cipher
置换幂运算:
pku1721 CARDS
pku3128 Leonardo's Notebook
推荐:(不错的应用)
pku3590 The shuffle Problem
简单题:(直接用套公式就可以了)
pku2409 Let it Bead
pku2154 Color
pku1286 Necklace of Beads
强烈推荐:(这题很不错哦,很巧妙)
pku2888 Magic Bracelet
pku3487 The Stable Marriage Problem
zoj1576 Marriage is Stable
简单:
pku3070 Fibonacci
pku3233 Matrix Power Series
pku3735 Training little cats
POJ3252
poj1850
poj1019
poj1942
简单题:
pku3517 And Then There Was One
pku1781 In Danger
pku1012 Joseph
pku2244 Eeny Meeny Moo
pku3372 Candy Distribution
pku3244 Difference between Triplets
pku1809 Regetni
pku1831 不定方程组
pku1737 Connected Graph
pku2480 Longge's problem
pku1792 Hexagonal Routes
pku3273Monthly Expense
pku3258River Hopscotch
pku1905Expanding Rods
pku3122Pie
pku1845 Sumdiv
poj3273
poj3258
poj1905
poj3122
poj3318
poj2454
poj3301
poj2031
poj1039
poj1408
poj1584
poj2187
poj1113
poj3384
poj2540
poj3130
poj3335
poj2079
poj1819
poj1066
poj2043
poj3227
poj2165
poj3429
]]>]]>Molly Hooper has n different kinds of chemicals arranged in a line. Each of the chemicals has an affection value, The i-th of them has affection value ai.
Molly wants Sherlock to fall in love with her. She intends to do this by mixing a contiguous segment of chemicals together to make a love potion with total affection value as a non-negative integer power of k. Total affection value of a continuous segment of chemicals is the sum of affection values of each chemical in that segment.
Help her to do so in finding the total number of such segments.
Molly Hooper has n different kinds of chemicals arranged in a line. Each of the chemicals has an affection value, The i-th of them has affection value ai.
Molly wants Sherlock to fall in love with her. She intends to do this by mixing a contiguous segment of chemicals together to make a love potion with total affection value as a non-negative integer power of k. Total affection value of a continuous segment of chemicals is the sum of affection values of each chemical in that segment.
Help her to do so in finding the total number of such segments.
The first line of input contains two integers, n and k, the number of chemicals and the number, such that the total affection value is a non-negative power of this number k. (1 ≤ n ≤ 105, 1 ≤ |k| ≤ 10).
Next line contains n integers a1, a2, ..., an ( - 109 ≤ ai ≤ 109) — affection values of chemicals.
Output a single integer — the number of valid segments.
4 2
2 2 2 2
8
4 -3
3 -6 -3 12
3
首先输入n,k。之后输入n个数,求子序列和为 k ^ m ( m = 0,1,2,3 ...) 的数量。
定义 sum[i] 为前i个数的和,则有 sum[i] - sum[j] == k ^ m, 但是算两次 sum[i] 和 sum[j] 时间复杂度就较大;我们来转化一下公式,使它变成 以下这样 sum[i] - k ^ m == sum[j] ,我们可以发现,如果存在 sum[i] - k ^ m 的值,那么就可以说明有这么一对 i , j 使结果存在,但是时间复杂度为 o( m lg n )。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <map>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cctype>
#include <set>
#define For(i,a,b) for(int (i)=(a);(i) < (b); ++(i))
#define Fors(i,a,b) for(int (i)=(a);(i) > (b); --(i))
#define sd(x) cout << "start debug:" << (x) << endl;
#define ed(x) cout << "end debug:" << (x) << endl;
#define rei(x) scanf("%d",&x)
#define rel(x) scanf("%lld",&x)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef queue<int> qi;
typedef long long LL;
const LL INF = 1e14;
int n;
LL k,sum[100050];
map<LL,LL>x;
set<LL>a;
set<LL>::iterator it;
int main()
{
while(~scanf("%d %lld",&n,&k))
{
x.clear();
a.clear();
sum[0]=0;
for(int i=1;i<=n;i++) scanf("%lld",&sum[i]),sum[i]+=sum[i-1];
a.insert(1);
LL temp=k;
for(int i=1;i<=60;i++){
if(temp>INF) break;
a.insert(temp);
temp*=k;
}
LL ans=0;
x[0]=1;
for(int i=1;i<=n;i++){
for(it=a.begin();it!=a.end();it++) ans+=x[sum[i]-*it];
x[sum[i]]++;
}
printf("%lld\n",ans);
}
return 0;
}
]]>"Good man never makes girls wait or breaks an appointment!" said the mandarin duck father. Softly touching his little ducks' head, he told them a story.
"Prince Remmarguts lives in his kingdom UDF – United Delta of Freedom. One day their neighboring country sent them Princess Uyuw on a diplomatic mission."
"Erenow, the princess sent Remmarguts a letter, informing him that she would come to the hall and hold commercial talks with UDF if and only if the prince go and meet her via the K-th shortest path. (in fact, Uyuw does not want to come at all)"
Being interested in the trade development and such a lovely girl, Prince Remmarguts really became enamored. He needs you - the prime minister's help!
DETAILS: UDF's capital consists of N stations. The hall is numbered S, while the station numbered T denotes prince' current place. M muddy directed sideways connect some of the stations. Remmarguts' path to welcome the princess might include the same station twice or more than twice, even it is the station with number S or T. Different paths with same length will be considered disparate.
The first line contains two integer numbers N and M (1 <= N <= 1000, 0 <= M <= 100000). Stations are numbered from 1 to N. Each of the following M lines contains three integer numbers A, B and T (1 <= A, B <= N, 1 <= T <= 100). It shows that there is a directed sideway from A-th station to B-th station with time T.
The last line consists of three integer numbers S, T and K (1 <= S, T <= N, 1 <= K <= 1000).
A single line consisting of a single integer number: the length (time required) to welcome Princess Uyuw using the K-th shortest path. If K-th shortest path does not exist, you should output "-1" (without quotes) instead.
2 2
1 2 5
2 1 4
1 2 2
14
为启发式算法中很重要的一种,被广泛应用在最优路径求解和一些策略设计的问题中。而A算法最为核心的部分,就在于它的一个估值函数的设计上:
其中是每个可能试探点的估值,它有两部分组成:一部分为,它表示从起始搜索点到当前点的代价(通常用某结点在搜索树中的深度来表示)。另一部分,即,它表示启发式搜索中最为重要的一部分,即当前结点到目标结点的估值,设计的好坏,直接影响着具有此种启发式函数的启发式算法的是否能称为A算法。
一种具有f(n)=g(n)+h(n)策略的启发式算法能成为A*算法的充分条件是:
一般的搜索前三条都可以满足,而第四点就要视情况而定了。
K短路的定义:假设从1出发,有M条长度不同的路径可以到达点N,则K短路就是这M条路径中第K小的路径长度。
以上所述,设f[n]为最终所求,则f(n)=g(n)+h(n);h(n)就是我们所说的‘启发式函数’,表示为重点t到其余一点p的路径长度,g(n)表示g当前从s到p所走的路径的长度。
即
估价函数=当前值+当前位置到终点的距离
解决思路:
如果当前为t的第k次出队,则当前路径的长度就是s到t的第k短路的长度,算法结束;
否则遍历与p相连的所有的边,将扩展出的到p的邻接点信息加入到优先级队列;
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
#define INF 0xffffff
#define MAXN 100010
struct node
{
int to;
int val;
int next;
};
struct node2
{
int to;
int g,f;
bool operator<(const node2 &r ) const
{
if(r.f==f)
return r.g<g;
return r.f<f;
}
};
node edge[MAXN],edge2[MAXN];
int n,m,s,t,k,cnt,cnt2,ans;
int dis[1010],visit[1010],head[1010],head2[1010];
void init()
{
memset(head,-1,sizeof(head));
memset(head2,-1,sizeof(head2));
cnt=cnt2=1;
}
void addedge(int from,int to,int val)
{
edge[cnt].to=to;
edge[cnt].val=val;
edge[cnt].next=head[from];
head[from]=cnt++;
}
void addedge2(int from,int to,int val)
{
edge2[cnt2].to=to;
edge2[cnt2].val=val;
edge2[cnt2].next=head2[from];
head2[from]=cnt2++;
}
bool spfa(int s,int n,int head[],node edge[],int dist[])
{
queue<int>Q1;
int inq[1010];
for(int i=0;i<=n;i++)
{
dis[i]=INF;
inq[i]=0;
}
dis[s]=0;
Q1.push(s);
inq[s]++;
while(!Q1.empty())
{
int q=Q1.front();
Q1.pop();
inq[q]--;
if(inq[q]>n)
return false;
int k=head[q];
while(k>=0)
{
if(dist[edge[k].to]>dist[q]+edge[k].val)
{
dist[edge[k].to]=edge[k].val+dist[q];
if(!inq[edge[k].to])
{
inq[edge[k].to]++;
Q1.push(edge[k].to);
}
}
k=edge[k].next;
}
}
return true;
}
int A_star(int s,int t,int n,int k,int head[],node edge[],int dist[])
{
node2 e,ne;
int cnt=0;
priority_queue<node2>Q;
if(s==t)
k++;
if(dis[s]==INF)
return -1;
e.to=s;
e.g=0;
e.f=e.g+dis[e.to];
Q.push(e);
while(!Q.empty())
{
e=Q.top();
Q.pop();
if(e.to==t)//找到一条最短路径
{
cnt++;
}
if(cnt==k)//找到k短路
{
return e.g;
}
for(int i=head[e.to]; i!=-1; i=edge[i].next)
{
ne.to=edge[i].to;
ne.g=e.g+edge[i].val;
ne.f=ne.g+dis[ne.to];
Q.push(ne);
}
}
return -1;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
addedge(a,b,c);
addedge2(b,a,c);
}
scanf("%d%d%d",&s,&t,&k);
spfa(t,n,head2,edge2,dis);
ans=A_star(s,t,n,k,head,edge,dis);
printf("%d\n",ans);
}
return 0;
}
]]>伸展树(英语:Splay Tree)是一种二叉查找树,它能在内完成插入、查找和删除操作。它是由丹尼尔·斯立特(Daniel Sleator)和羅伯特·塔揚在1985年发明的。
在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行調整,把被查找的条目搬移到离树根近一些的地方。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
它的优势在于不需要记录用于平衡树的冗余信息。
伸展树(英语:Splay Tree)是一种二叉查找树,它能在内完成插入、查找和删除操作。它是由丹尼尔·斯立特(Daniel Sleator)和羅伯特·塔揚在1985年发明的。
在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行調整,把被查找的条目搬移到离树根近一些的地方。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
它的优势在于不需要记录用于平衡树的冗余信息。
把重要的树节点转移树根。使在最坏的情况下,查询时间复杂度仍然为。
具体证明请看维基百科的。 Splay tree - Wikipedia
首先讲基本旋转之一右旋(zig):
解释下上面这个图,对于右旋,P为目标节点,Q为P的父节点,A、B、C则是相关子节点。右旋操作可以将P节点变为Q的父节点,且保持原有的二叉查询树的特性,即对于某一节点,其左子树的所有节点的值都小于该节点而右子树的所有节点的值都大于该节点。
整个过程分为三步:
####右旋代码:
void right_rotate(long P)
{
long Q = father[P];
long Z = father[Q];//这里的z与上文的Q相关,表示y的父节点。
leftson[Q] = rightson[P];//第一步,将P的右子树B(如果有的话)作Q的左子树。
if(rightson[P] != 0)
father[rightson[P]] = Q;//第一步,B认Q为父。
father[P] = Z;//第二步,P认Z做爹。
if(z != 0){
if(leftson[Z] == y)//判断原来的Q是Z的左节点还是有节点。
leftson[Z] = P;//第二步,P做Z儿子。
else
rightson[Z] = P;
}
rightson[P] = y;//第三步,Q作为P的右子树
father[Q] = P;//第三步,Q认P作爹
}
接着另一个旋转——左旋(zag):
有了上面右旋的基础,就直接说明步骤了:
void left_rotate(long P)
{
long Q = father[P];
long Z = father[Q];
rightson[Q] = leftson[P];
if(leftson[P] != 0)
father[leftson[P]] = Q;
father[P]=Z;
if(Z != 0){
if(leftson[Z] == Q)
leftson[Z] = P;
else
rightson[Z] = P;
}
leftson[P] = Q;
father[Q] = P;
}
把重要节点通过旋转操作转移为目标节点的父节点。一般为转移到根节点。
####Splay代码
void splaQ(long P,long Ancestry){
while(father[P] != Ancestry){
long Q = father[P];
long Z = father[Q];
if(Z == Ancestry){ //父节点为目标节点
if(rightson[Q] == P)
left_rotate(P);
else
right_rotate(P);
}
else{//由左旋和右旋组合产生四种情况
if(rightson[Z] == Q && rightson[Q] == P){
left_rotate(Q);
left_rotate(P);
}
else if(rightson[Z] == Q && leftson[Q] == P){
right_rotate(P);
left_rotate(P);
}
else if(leftson[Z] == Q && leftson[Q] == P){
right_rotate(Q);
right_rotate(P);
}
else{
left_rotate(P);
right_rotate(P);
}
}
}
if(Ancestry == 0)
root = P;
}
void insert(int x)
{
if (root==0){ //若当前为空树则直接加
tree[++sz].num=x;
childsz[sz]=cnt[sz]=1; //size为该子树的节点数目,cnt为同一数值数字数目
root = sz; //sz为整棵树的节点数目
return;
}
int now = root,fa = 0; //now,root等都为节点编号。
while (1){
if (val[now] == x){ //相等,找到位置
tree[now].cnt++;
splay(now);
return;
}
fa = now;
now = (x>val[now]?rightson[now]:leftson[now]); //按大小找子节点
if (now == 0){ //该点未被添加过,新建节点
sz++; //节点新建了一个,作为节点编号
val[sz] = x;
cnt[sz] = childsize[sz] = 1;
father[sz] = fa;
(x > val[fa] ? rightson[fa] : leftson[fa]) = sz; //设置父亲
splay(sz);
return;
}
}
}
###查找操作
和其它二叉搜索树的操作基本一样。但是区别是:
不要忘记了再splay一下
int find(int v){
int ans = 0,now = root;
while (1){
if (v < val[now])
now = leftch[now];
else{
ans += (leftch[now] ? size[leftch[now]] : 0);
//左子树存在的话就为左子树大小,否则为0。
if (v == val[now]) {
splay(now);
return ans+1;
}
ans += cnt[now];//根的大小
now = rightch[now];
}
}
return ans;
}
如果当前点有左子树,并且x比左子树的大小小的话,即向左子树寻找;
否则,向右子树寻找:先判断是否有右子树,然后记录右子树的大小以及当前点的大小(都为权值),用于判断是否需要继续向右子树寻找。
int findx(int x){
int now = root;
while (1){
if (leftch[now] && x <= size[leftch[now]])
now = ch[now][0];
else{
int temp = (leftchch[now] ? size[leftch[now]] : 0) + cnt[now];
if (x <= temp)
return key[now];
x -= temp;
now = rightch[now];
}
}
}
##感谢
http://blog.csdn.net/clove_unique/article/details/50630280
http://blog.csdn.net/skydec/article/details/20151805
]]>有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。例如N = 2。A只能拿1颗,所以B可以拿到最后1颗石子。
]]>有一堆石子共有N个。A B两个人轮流拿,A先拿。每次只能拿1,3,4颗,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N,问最后谁能赢得比赛。例如N = 2。A只能拿1颗,所以B可以拿到最后1颗石子。
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)第2 - T + 1行:每行1个数N。(1 <= N <= 10^9)
共T行,如果A获胜输出A,如果B获胜输出B。
3234
BAA
从1个石子推下去发现7个一循环
include<stdio.h>
int main()
{
freopen("a.txt","r",stdin);
long long N,K;
int t;
scanf("%d",&t);
int bo[7]={0,1,0,1,1,1,1};
while(t--){
scanf("%d",&N);
N%=7;
printf("%d ",N);
if(bo[N])
printf("A\n");
else
printf("B\n");
}
}
]]>The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 x
where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
5 6 7 8 5 6 7 8 5 6 7 8 5 6 7 8
9 x 10 12 9 10 x 12 9 10 11 12 9 10 11 12
13 14 11 15 13 14 11 15 13 14 x 15 13 14 15 x
The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.
Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.
You will receive, several descriptions of configuration of the 8 puzzle. One description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle
1 2 3
x 4 6
7 5 8
is described by this list:
1 2 3 x 4 6 7 5 8
You will print to standard output either the word ``unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line. Do not print a blank line between cases.
2 3 4 1 5 x 7 6 8
ullddrurdllurdruldr
这题核心思想是使用A*算法,并且需要通过hash确定每种状态的序号,以用来访问数组来确定时候被访问过和父节点的存储。
首先hash的核心算法是康托展开。其次A*算法使用估值函数确定最优节点,以放入队列中进行搜索。A*算法的参数有三个f、h、g,f = h + g,g为已花费的代价(题中指已经走过的步数),h为估算的代价(题中指在无障碍的情况下最少还需要多少步到达终点目标),正式地,这个距离叫做曼哈顿距离。
最后一点,每次数字的移动会使状态的逆序数改变,但是不改变奇偶性。
include<iostream>
include<cstdio>
include<cstring>
include<queue>
include<cmath>
using namespace std;
struct node //状态
{
int a[10];
int f, h, g;
int x; //x在的位置
friend bool operator < (node a, node b)
{
return a.f > b.f;
}
};
priority_queue<node>que;
int fac[10];
//46233
struct
{
int father;
char dir;
}vis[362881];
int get_h(int a[])
{
int h = 0;
for(int i = 0; i < 8; i++)
{
if(a[i])
h += fabs((a[i]-1)/3 - i/3) + fabs((a[i]-1)%3 - i%3);
}
return h;
}
int Hash(int a[])
{
int ans = 0;
for(int i = 0; i < 9; i++)
{
int tmp = 0;
for(int j = i+1; j < 9; j++)
{
if(a[i] > a[j]) tmp++;
}
ans += tmp*fac[8-i];
}
return ans+1;
}
void prin(int n)
{
// printf("n=%d\n", n);
if(vis[n].father!=-1)
{
prin(vis[n].father);
printf("%c", vis[n].dir);
}
}
void SWAP(int &x, int &y)
{
int t = x;
x = y;
y = t;
}
int dir[4][2] = { {1, 0}, {-1, 0}, {0, -1}, {0, 1} };
char dd[] = "dulr";
bool is(int a[])
{
int ans = 0;
for(int i = 0; i < 9; i++)
{
if(a[i])
for(int j = i+1; j < 9; j++)
{
if(a[i] > a[j] && a[j])
ans++;
}
}
return !(ans & 1);
}
void debug(int a[])
{
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
printf("%d ", a[i*3+j]);
}
printf("\n");
}
printf("\n");
}
int bfs(node star)
{
while(!que.empty()) que.pop();
que.push( star );
star.h = get_h( star.a ); star.g = 0;
star.f = star.g + star.h;
vis[ Hash( star.a ) ].father = -1;
while(!que.empty()){
node tmp = que.top(); que.pop();
int father = Hash(tmp.a);
for(int i = 0; i < 4; i++){
int x = dir[i][0] + tmp.x/3;
int y = dir[i][1] + tmp.x%3;
if(0 <= x && x < 3 && 0 <= y && y < 3){
node s = tmp; s.x = x*3+y;
SWAP( s.a[ tmp.x ], s.a[ s.x ] );
s.g++;
s.h = get_h( s.a ); s.f = s.h + s.g;
int son = Hash(s.a);
if(son == 46234){
vis[ son ].father = father;
vis[ son ].dir = dd[i];
prin(46234);printf("\n");
return 0;
}
if(!vis[ son ].father && is(s.a)){
vis[ son ].father = father;
vis[ son ].dir = dd[i];
que.push( s );
}
}
}
}
return 1;
}
int main(void)
{
int i;
fac[1] = 1;
for(i = 2; i < 10; i++) fac[i] = fac[i-1]*i;
node star;
char in[2];
// freopen("ou.txt", "w", stdout);
while(~scanf("%s", in))
{
memset(vis, 0, sizeof(vis));
if(in[0] == 'x')
{
star.a[0] = 0;
star.x = 0;
}
else star.a[0] = in[0] - '0';
for(i = 1; i < 9; i++)
{
scanf("%s", in);
if(in[0] == 'x')
{
star.a[i] = 0;
star.x = i;
}
else star.a[i] = in[0] - '0';
}
if(!is(star.a))
{
printf("unsolvable\n");continue;
}
if(Hash(star.a) == 46234) {printf("\n"); continue;}
if(bfs(star))
{
printf("unsolvable\n");
}
}
return 0;
}
]]>并查集(union-find sets)是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。
Make_Set(x) 把每一个元素初始化为一个集合:
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
Find_Set(x) 查找一个元素所在的集合:
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
Union(x,y) 合并x,y所在的两个集合:
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先。具体过程为,利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。
Find_Set(x)时 路径压缩:
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了。
Union(x,y)时 按秩合并:
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。
上次Gardon的迷宫城堡小希玩了很久(见Problem B),现在她也想设计一个迷宫让Gardon来走。但是她设计迷宫的思路不一样,首先她认为所有的通道都应该是双向连通的,就是说如果有一个通道连通了房间A和B,那么既可以通过它从房间A走到房间B,也可以通过它从房间B走到房间A,为了提高难度,小希希望任意两个房间有且仅有一条路径可以相通(除非走了回头路)。小希现在把她的设计图给你,让你帮忙判断她的设计图是否符合她的设计思路。比如下面的例子,前两个是符合条件的,但是最后一个却有两种方法从5到达8。
输入包含多组数据,每组数据是一个以0 0结尾的整数对列表,表示了一条通道连接的两个房间的编号。房间的编号至少为1,且不超过100000。每两组数据之间有一个空行。
整个文件以两个-1结尾。
对于输入的每一组数据,输出仅包括一行。如果该迷宫符合小希的思路,那么输出"Yes",否则输出"No"。
6 8 5 3 5 2 6 4
5 6 0 0
8 1 7 3 6 2 8 9 7 5
7 4 7 8 7 6 0 0
3 8 6 8 6 4
5 3 5 6 5 2 0 0
-1 -1
Yes
Yes
No
此题是并查集的基本应用。只需要使用并查集判断是否有环路 且 是否只有单个集合。
有坑。。。 输入 0 0 输出Yes
include <iostream>
include <cstdio>
include <cstring>
using namespace std;
const int MAX = 100005;
int flag = 0,maxx=0;
int vis[MAX],pre[MAX]; // pre用来存index的父节点,路径压缩后存的是根节点。
//vis用来判断是否用到这个点。
void init(){ //初始化
for (int i = 0; i < MAX; ++i)
{
pre[i] = i;
}
memset(vis,0,sizeof(vis));
flag = 0;
maxx=0;
}
int find(int v){ // find操作
int r = v;
while(pre[r] != r){ // 寻到当前状态的根节点
r = pre[r];
}
int i = v,j;
while(i != r){ // 回溯过程 将所有子节点的pre改为父节点。
j = pre[i];
pre[i] = r;
i = j;
}
return r;
}
void join(int v1,int v2){
int newX = find(v1),newY = find(v2);
if(newX == newY && v1 != v2) // 使用并查集判断时候有环路。
{
flag = 1;
}
else
{
pre[newX] = newY; // 吧点加入集合
}
}
void isconnect(){ // 判断是否有只有单个图。
int val = find(pre[maxx]);
for (int i = 0; i < maxx; ++i)
{
if(vis[i] != 0){
if(find(pre[i]) != find(pre[maxx])){
flag = 1;
break;
}
}
}
}
int main()
{
int x,y;
while(scanf("%d%d",&x,&y))
{
init();
if(x==0&&y==0){
printf("Yes\n");
continue;
}
if(x == -1 && y == -1)
break;
vis[x] = 1;
vis[y] = 1;
maxx = max(maxx,max(x,y));
join(x,y);
while(scanf("%d%d",&x,&y) && x && y){
vis[x] = 1;
vis[y] = 1;
maxx = max(maxx,max(x,y));
join(x,y);
}
isconnect();
if(flag)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
]]>Agrael likes play a simple game with his friend Animal during the classes. In this Game there are n piles of stones numbered from 1 to n, the 1st pile has M1 stones, the 2nd pile has M2 stones, ... and the n-th pile contain Mn stones. Agrael and Animal take turns to move and in each move each of the players can take at most L1 stones from the 1st pile or take at most L2 stones from the 2nd pile or ... or take Ln stones from the n-th pile. The player who takes the last stone wins.
After Agrael and Animal have played the game for months, the teacher finally got angry and decided to punish them. But when he knows the rule of the game, he is so interested in this game that he asks Agrael to play the game with him and if Agrael wins, he won't be punished, can Agrael win the game if the teacher and Agrael both take the best move in their turn?
The teacher always moves first(-_-), and in each turn a player must takes at least 1 stones and they can't take stones from more than one piles.
The first line contains the number of test cases. Each test cases begin with the number n (n ≤ 10), represent there are n piles. Then there are n lines follows, the i-th line contains two numbers Mi and Li (20 ≥ Mi > 0, 20 ≥ Li > 0).
Your program output one line per case, if Agrael can win the game print "Yes", else print "No".
2
1
5 4
2
1 1
2 2
Yes
No
此题有两种办法一种是nim+bash,还有一种是通过SG函数来解决
]]>虽然草儿是个路痴(就是在杭电待了一年多,居然还会在校园里迷路的人,汗~),但是草儿仍然很喜欢旅行,因为在旅途中 会遇见很多人(白马王子,0),很多事,还能丰富自己的阅历,还可以看美丽的风景……草儿想去很多地方,她想要去东京铁塔看夜景,去威尼斯看电影,去阳明山上看海芋,去纽约纯粹看雪景,去巴黎喝咖啡写信,去北京探望孟姜女……眼看寒假就快到了,这么一大段时间,可不能浪费啊,一定要给自己好好的放个假,可是也不能荒废了训练啊,所以草儿决定在要在最短的时间去一个自己想去的地方!因为草儿的家在一个小镇上,没有火车经过,所以她只能去邻近的城市坐火车(好可怜啊~)。
输入数据有多组,每组的第一行是三个整数T,S和D,表示有T条路,和草儿家相邻的城市的有S个,草儿想去的地方有D个;
接着有T行,每行有三个整数a,b,time,表示a,b城市之间的车程是time小时;(1=<(a,b)<=1000;a,b 之间可能有多条路)
接着的第T+1行有S个数,表示和草儿家相连的城市;
接着的第T+2行有D个数,表示草儿想去地方。
输出草儿能去某个喜欢的城市的最短时间。
6 2 3
1 3 5
1 4 7
2 8 12
3 8 4
4 9 12
9 10 2
1 2
8 9 10
9
对于集合到集合的最短路题,一种简单的做法是构建两个虚拟点,一个是虚拟起点,一个是虚拟终点,虚拟起点到起点集合的各个点的距离为0,虚拟终点到终点集合的各个点的距离为0。然后对虚拟起点和终点求一边最短路即可求得答案。
include <iostream>
include <cstring>
include <vector>
include <queue>
include <cstdio>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge {
int vertex, weight;
};
class Graph {
private:
int n;
vector<Edge> * edges;
bool * visited;
public:
int * dist;
Graph (int input_n) {
n = input_n;
edges = new vector<Edge>[n];
dist = new int[n];
visited = new bool[n];
memset(visited, 0, n);
memset(dist, 0x3f, n * sizeof(int));
}
~Graph() {
delete[] dist;
delete[] edges;
delete[] visited;
}
void insert(int x, int y, int weight) {
edges[x].push_back(Edge{y, weight});
edges[y].push_back(Edge{x, weight});
}
void dijkstra(int v) {
dist[v] = 0;
for(int i = 0;i < n;i++){
int min_dist = INF,min_vertex;
for(int j = 0;j < n; j++){
if(!visited[j] && dist[j] < min_dist){
min_dist = dist[j];
min_vertex = j;
}
}
visited[min_vertex] = 1;
for(Edge &j: edges[min_vertex]){
if( min_dist + j.weight < dist[j.vertex]){
dist[j.vertex] = min_dist + j.weight;
}
}
}
}
};
int main() {
int T,S,D,maxn;
while(scanf("%d%d%d",&T,&S,&D)!=EOF){
Graph g(1010);
maxn = 0;
for (int i = 0; i < T; i++) {
int a, b, c;
cin >> a >> b >> c;
maxn = max(max(a,b),maxn);
g.insert(a, b, c);
}
maxn++;
for (int i = 0; i < S; i++) {
int tmp;
cin >> tmp;
g.insert(0, tmp, 0);
}
for (int i = 0; i < D; i++) {
int tmp;
cin >> tmp;
g.insert(maxn, tmp, 0);
}
g.dijkstra(0);
cout << g.dist[maxn] << endl;
}
return 0;
}
]]>A school bought the first computer some time ago(so this computer's id is 1). During the recent years the school bought N-1 new computers. Each new computer was connected to one of settled earlier. Managers of school are anxious about slow functioning of the net and want to know the maximum distance Si for which i-th computer needs to send signal (i.e. length of cable to the most distant computer). You need to provide this information.
Hint: the example input is corresponding to this graph. And from the graph, you can see that the computer 4 is farthest one from 1, so S1 = 3. Computer 4 and 5 are the farthest ones from 2, so S2 = 2. Computer 5 is the farthest one from 3, so S3 = 3. we also get S4 = 4, S5 = 4.
Input file contains multiple test cases.In each case there is natural number N (N<=10000) in the first line, followed by (N-1) lines with descriptions of computers. i-th line contains two natural numbers - number of computer, to which i-th computer is connected and length of cable used for connection. Total length of cable does not exceed 10^9. Numbers in lines of input are separated by a space.
For each case output N lines. i-th line must contain number Si for i-th computer (1<=i<=N).
5
1 1
2 1
3 1
1 1
3
2
3
4
4
此题的意思为:树中一共n个节点,输出每个节点到其中任意一个节点的最远花费的值。
有这么一个定理:
首先假设树的最长路的两个叶子节点为v1,v2,那么现有结论,从任意一点u出发走到的最远的点一定是(v1,v2)中的一点,然后再从v1或者v2出发走到的最远点一定是v2或者v1。
因此,经过三次搜索就能找到任意一点最长路径或者说是话费。
include <iostream>
include <cstdio>
include <algorithm>
include <vector>
include <queue>
include <cmath>
include <cstring>
using namespace std;
const int MAX = 10010;
struct Tree{
int v;
int dis;
};
vector <struct Tree > tree[MAX];
int ans[MAX];
int max_len,root;
init(){
memset(ans,0,sizeof(ans));
memset(tree,0,sizeof(tree));
}
void trees(int u,int v,int len){ // v为当前节点,u为父节点
if(max_len < len ){
max_len = len;
root = v;
}
for(int i = 0;i < tree[v].size();i++){
struct Tree new_v = tree[v][i];
if(new_v.v == u)
continue;
trees(v,new_v.v,len + new_v.dis);
ans[new_v.v] = max (ans[new_v.v],new_v.dis + len);
}
}
int main()
{
int n;
while(scanf("%d",&n) != EOF){
init();
int max_w = 0, w = 1;
for(int i = 2;i <= n; i++){
int tmp,tmp_d; cin >> tmp >> tmp_d;
tree[tmp].push_back(Tree{i,tmp_d});
tree[i].push_back(Tree{tmp,tmp_d});
}
max_len = 0;
trees(-1,1,0);
trees(-1,root,0);
trees(-1,root,0);
for(int i = 1;i <= n; i++){
cout << ans[i] <<endl;
}
}
return 0;
}
]]>There are a group of students. Some of them may know each other, while others don't. For example, A and B know each other, B and C know each other. But this may not imply that A and C know each other.
Now you are given all pairs of students who know each other. Your task is to divide the students into two groups so that any two students in the same group don't know each other.If this goal can be achieved, then arrange them into double rooms. Remember, only paris appearing in the previous given set can live in the same room, which means only known students can live in the same room.
Calculate the maximum number of pairs that can be arranged into these double rooms.
For each data set:
The first line gives two integers, n and m(1<n<=200), indicating there are n students and m pairs of students who know each other. The next m lines give such pairs.
Proceed to the end of file.
If these students cannot be divided into two groups, print "No". Otherwise, print the maximum number of pairs that can be arranged in those rooms.
4 4
1 2
1 3
1 4
2 3
6 5
1 2
1 3
1 4
2 5
3 6
No
3
这道题要先判断图是不是二分图,如果不是的话,就直接输出No,是的话就求最大匹配,
建边是双向的所以要除以2。
先判断能否构成二分图,判断二分图用交叉染色法从某个未染色的点出发把此点染成白
色,该点周围的点染成黑色黑色周围的又染成白色,若走到某个点已经染色并且它相邻
点的颜色与它一样则不是二分图,而是有奇数圈的图可以这样理解,染白色既加入X集
合,黑色既加入Y集合若某个点即是X集合又是Y集合,那说明不是二分图。
其次用匈牙利算法计算最大匹配,是通过DFS搜增广路来计算最大匹配。通过搜增广路扩展匹配路径。具体为:
include"stdio.h"
include"string.h"
include"iostream"
include"queue"
using namespace std;
define N 205
int mark[N],link[N],map[N][N],color[N],n;
int find(int a) //匈牙利算法
{
int i;
for(i=1;i<=n;i++){
if(!mark[i]&&map[a][i]){
mark[i]=1;
if(!link[i]||find(link[i])){ //若i已经配对,则查找和i配对的那个元素是否还能和其他元素配对
link[i]=a; //若可以则把i配给a
return 1;
}
}
}
return 0;
}
int judge() //判断是否是二分图
{
int i,cur;
queueq; //队列声明
q.push(1); //把1加入队列
while(!q.empty()){
cur=q.front(); //取队首元素
q.pop(); //删除队首元素
for(i=1;i<=n;i++){
if(map[cur][i]){ //1和2、3认识则把2、3均标记为2;
if(color[i]==-1){
color[i]=1-color[cur];
q.push(i);
}
else if(color[i]==color[cur]) //接下来到2出对时,color[3]=color
return 0;
}
}
}
return 1;
}
int main()
{
int i,j,m,ans;
while(scanf("%d%d",&n,&m)!=-1){
memset(link,0,sizeof(link));
memset(map,0,sizeof(map));
memset(color,-1,sizeof(color));
while(m--){
scanf("%d%d",&i,&j);
map[i][j]=map[j][i]=1;
}
if(judge()==0){
printf("No\n");
continue;
}
ans=0;
for(i=1;i<=n;i++){
memset(mark,0,sizeof(mark));
ans+=find(i);
}
printf("%d\n",ans/2);
}
return 0;
}
]]>在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助他们吗?
输入包括多组数据。每组数据第一行是两个整数N、M(N<=100,M<=10000),N表示成都的大街上有几个路口,标号为1的路口是商店所在地,标号为N的路口是赛场所在地,M则表示在成都有几条路。N=M=0表示输入结束。接下来M行,每行包括3个整数A,B,C(1<=A,B<=N,1<=C<=1000),表示在路口A与路口B之间有一条路,我们的工作人员需要C分钟的时间走过这条路。
输入保证至少存在1条商店到赛场的路线。
对于每组输入,输出一行,表示工作人员从商店走到赛场的最短时间
2 1
1 2 3
3 3
1 2 5
2 3 5
3 1 2
0 0
3
2
此题解包括dijkstra的讲解。
**dijistra的简述:**将源点(初始点)放入一个空集合U,初始化距离(指图中的任意一点距离源点最短的距离)为0,由源点开始广度遍历,寻找到离源点最近的相邻点后加入U中,从这个点V继续广度遍历,对于每个相邻的点P,对比已知距离和由当前点V加V、P两点间边的权值,取最小后更新,在这些相邻的点中取离源点最近的点,加入集合U中,重复操作,直到U的大小等于图的点的集合。
题目简述: 简单的最短路。
include <iostream>
include <cstring>
include <vector>
include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
struct Edge {
int vertex, weight;
};
class Graph {
private:
int n;
vector<Edge> * edges;
bool * visited;
public:
int * dist;
Graph (int input_n) {
n = input_n;
edges = new vector<Edge>[n];
dist = new int[n];
visited = new bool[n];
memset(visited, 0, n);
memset(dist, 0x3f, n * sizeof(int));
}
~Graph() {
delete[] dist;
delete[] edges;
delete[] visited;
}
void insert(int x, int y, int weight) {
edges[x].push_back(Edge{y, weight});
edges[y].push_back(Edge{x, weight});
}
void dijkstra(int v) {
dist[v] = 0;
for(int i = 0;i < n;i++){
int min_dist = INF,min_vertex;
for(int j = 0;j < n; j++){
if(!visited[j] && dist[j] < min_dist){
min_dist = dist[j];
min_vertex = j;
}
}
visited[min_vertex] = 1;
for(Edge &j: edges[min_vertex]){
if( min_dist + j.weight < dist[j.vertex]){
dist[j.vertex] = min_dist + j.weight;
}
}
}
}
};
int main() {
int n, m;
while ((cin >> n >> m) && n && m){
Graph g(n);
for (int i = 0; i < m; i++) {
int a, b, c;
cin >> a >> b >> c;
g.insert(a-1, b-1, c);
}
g.dijkstra(0);
cout << g.dist[n-1] << endl;
}
return 0;
}
]]>Yifenfei very like play a number game in the n*n Matrix. A positive integer number is put in each area of the Matrix.
Every time yifenfei should to do is that choose a detour which frome the top left point to the bottom right point and than back to the top left point with the maximal values of sum integers that area of Matrix yifenfei choose. But from the top to the bottom can only choose right and down, from the bottom to the top can only choose left and up. And yifenfei can not pass the same area of the Matrix except the start and end.
The input contains multiple test cases.
Each case first line given the integer n (2<n<30)
Than n lines,each line include n positive integers.(<100)
For each test case output the maximal values yifenfei can get.
2
10 3
5 10
3
10 3 3
2 5 3
6 7 10
5
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
28
46
80
这个题目是一道 多线程DP ,题目简单的可理解为,在矩阵中选择两条不相交的最优路径,最优理解为使路径上数字的和最大。简单分析之后,可以发现此题由原来方向为左上 -> 右下 -> 左上 的路径可转换为两条方向为左上 -> 右下 的路径同时出发,且不相交,**多线程 ** 得到体现。
规定dp[x1][y1][x2][y2]表示为两条路径的终点分别为(x1 ,y1),(x2 ,y2),原矩阵数据存在MAP[][]中。对于当前时刻的状态,因为一个点的位置的前一个状态有两种可能性,所以一共有四种可能性,状态转移方程具体为:
dp[x1][y1][x2][y2] = max(dp[x1-1][y1][x2-1][y2],dp[x1-1][y1][x2][y2-1],dp[x1][y1-1][x2-1][y2],dp[x1][y1-1][x2][y2-1]) + MAP[x1][y1]
当x1 != x2 或 y1 != y2 时dp[x1][y1][x2][y2] += MAP[x2][y2]
分析下时间复杂度为O(n4),空间复杂度为O(n4)。如何优化呢?
有这么一个事实,当明确一个终点的距离原点的距离后,如果知道该终点的横左边,那么由距离可知唯一的纵左边,并且两个不同的重点有唯一相同属性到原点的距离 ,即x1 + y1 = x2 + y2。
那么我们可以设一个变量L,用来存坐标与原点的距离。dp[l][x1][x2] 则可以简单的表示出原来的dp[x1][y1][x2][y2] ,但是空间与时间复杂度变为O(n3).
include <iostream>
include <cstdio>
include <cstring>
using namespace std;
int max(int a,int b,int c,int d){
int tmp1 = max(a,b);
int tmp2 = max(c,d);
return max(tmp1,tmp2);
}
int main()
{
int n;
int MAP[40][40],dp[80][40][40];
while (scanf("%d",&n) != EOF ){
memset(MAP,0,sizeof(MAP));
memset(dp,0,sizeof(dp));
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
scanf("%d",&MAP[i][j]);
}
}
for (int l = 1; l <= (2 * n - 1); ++l)
for (int x1 = 1; x1 <= n; ++x1)
for (int x2 = 1; x2 <=n; ++x2){
int tmp_y1 = l - x1 + 1,tmp_y2 = l - x2 + 1;
dp[l][x1][x2] = max(dp[l-1][x1-1][x2-1],dp[l-1][x1][x2],
dp[l-1][x1-1][x2],dp[l-1][x1][x2-1]) + MAP[x1][tmp_y1];
if (x1 != x2 || tmp_y1 != tmp_y2)
dp[l][x1][x2] += MAP[x2][tmp_y2];
}
cout << dp[2*n-1][n][n]<<endl;
}
return 0;
}
]]>有N堆石子。A B两个人轮流拿,A先拿。每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N及每堆石子的数量,问最后谁能赢得比赛。
]]>有N堆石子。A B两个人轮流拿,A先拿。每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。假设A B都非常聪明,拿石子的过程中不会出现失误。给出N及每堆石子的数量,问最后谁能赢得比赛。
3堆石子,每堆1颗。A拿1颗,B拿1颗,此时还剩1堆,所以A可以拿到最后1颗石子。
定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。
更严谨的定义是:
1.无法进行任何移动的局面(也就是terminal position)是P-position;
2.可以移动到P-position的局面是N-position;
3.所有移动都导致N-position的局面是P-position。
按照这个定义,如果局面不可能重现,或者说positions的集合可以进行拓扑排序,那么每个position或者是P-position或者是N-position,而且可以通过定义计算出来。
结论:对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1 ^ a2 ^ ... ^ an=0,其中^表示异或(xor)运算。
简单证明
根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题:
第一个命题,显然terminal position只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an),若a1 ^ a2 ^ ... ^ an != 0,一定存在某个合法的移动,将ai改变成ai'后满足a1 ^ a2 ^ ... ^ ai' ^ ... ^ an = 0。不妨设a1 ^ a2 ^ ... ^ an = k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai ^ k < ai一定成立。则我们可以将ai改变成ai' = ai ^ k,此时a1 ^ a2 ^ ... ^ ai' ^ ... ^ an = a1 ^ a2 ^ ... ^ an ^ k=0。
第三个命题,对于某个局面(a1,a2,...,an),若a1 ^ a2 ^ ... ^ an = 0,一定不存在某个合法的移动,将ai改变成ai'后满足a1 ^ a2 ^ ... ^ ai' ^ ... ^ an = 0。因为异或运算满足消去率,由a1 ^ a2 ^ ... ^ an = a1 ^ a2 ^ ... ^ ai' ^ ... ^ an可以得到ai = ai'。所以将ai改变成ai'不是一个合法的移动。证毕。
根据这个定理,我们可以在O(n)的时间内判断一个Nim的局面的性质,且如果它是N-position,也可以在O(n)的时间内找到所有的必胜策略。Nim问题就这样基本上完美的解决了。
当题目条件增加‘最多取k个’时,对每一堆石子mod(k+1)。
include <stdio.h>
int main() {
int n, x, r = 0;
scanf( "%d", &n );
while( n-- ) {
scanf( "%d", &x );
r ^= x;
}
printf( "%c\n", r == 0 ? 'B' : 'A' );
return 0;
}
]]> 有2堆石子。A,B两个人轮流拿,A先拿。每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取。拿到最后1颗石子的人获胜。假设A,B都非常聪明,拿石子的过程中不会出现失误。给出2堆石子的数量,问最后谁能赢得比赛。
]]> 有2堆石子。A,B两个人轮流拿,A先拿。每次可以从一堆中取任意个或从2堆中取相同数量的石子,但不可不取。拿到最后1颗石子的人获胜。假设A,B都非常聪明,拿石子的过程中不会出现失误。给出2堆石子的数量,问最后谁能赢得比赛。
2堆石子分别为3颗和5颗。那么不论A怎样拿,B都有对应的方法拿到最后1颗。
我们用,,,,表示两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,$a_0=b_0=0$ 、 $ a_k$是未在前面出现过的最小自然数,而 $b_k= a_k + k$
,奇异局势有如下三条性质:
1.任何自然数都包含在一个且仅有一个奇异局势中。
由于是未在前面出现过的最小自然数,所以有,而 。所以性质1成立。
2.任意操作都可将奇异局势变为非奇异局势。
事实上,若只改变奇异局势(,)的某一个分量,那么另一个分量不可能在其他奇异局势中,所以必然是非奇异局势。如果使(,)的两个分量同时减少,则由于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。
3.采用适当的方法,可以将非奇异局势变为奇异局势。
假设面对的局势是(),若,则同时从两堆中取走 a 个物体,就变为了奇异局势(0,0);如果,,那么,取走个物体,即变为奇异局势;如果 , ,则同时从两堆中拿走个物体,变为奇异局势;如果,则从第一堆中拿走多余的数量即可;如果, ,分两种情况,第一种,,从第二里面拿走 即可;第二种,,从第二堆里面拿走 即可。
从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
,,方括号表示取整函数奇妙的是其中出现了黄金分割数,因此发现对于任意的局势(a,b),a<b, 根据上述公式,其中k明显为a与b的差值,也正是根据这个差值来确认(a,b)是否为奇异局势。具体过程为,如果则为奇异局势。
include <stdio.h>
include <math.h>
include <iostream>
using namespace std;
int main() {
int t, a, b, m;
cin >> t;
while (t--) {
cin >> a >> b;
if (a > b)
swap(a,b);
m = (int)((b-a) * (1 + sqrt(5)) / 2.0);
printf("%s\n", a == m ? "B" : "A");
}
return 0;
}
]]>容斥原理的简单描述如下:
要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分。简单的说,就是对所有单个集合求和后减去单数个集合相交部分,加上双数集合相交部分。
]]>容斥原理的简单描述如下:
要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分。简单的说,就是对所有单个集合求和后减去单数个集合相交部分,加上双数集合相交部分。
原理公式:
给定 ,求1到n的整数中至少能整除a中一个元素有几个?
include <iostream>
using namespace std;
int a[4] = {2,3,5,7};
int n = 100,m = 4;
typedef long long ll;
ll gcd(ll a,ll b){
ll t;
if (!a || !b)return 0;
if (a < b){t = b; b = a; a = t;}
while (b != 0 ){ t = a % b; a = b; b = t;}
return a;
}
void solve(){
ll res = 0;
for (int i = 1; i < (1 << m); i++){
int num = 0;
for(int j = i;j != 0; j >>= 1)
num += j & 1;
ll lcm = 1;
for (int j = 0; j < m; j++){
if (i >> j & 1){
lcm = lcm /gcd(lcm,a[j]) * a[j];
if (lcm > n) break;
}
}
if (num % 2 == 0) res -= n /lcm;
else res += n / lcm;
cout << res <<endl;
}
cout << res <<endl;
}
int main()
{
solve();
return 0;
}
]]>有一堆石子共有N个。A,B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A,B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。
]]>有一堆石子共有N个。A,B两个人轮流拿,A先拿。每次最少拿1颗,最多拿K颗,拿到最后1颗石子的人获胜。假设A,B都非常聪明,拿石子的过程中不会出现失误。给出N和K,问最后谁能赢得比赛。
N = 3,K = 2。无论A如何拿,B都可以拿到最后1颗石子。
显然,如果N=K+1,那么由于一次最多只能取K个,所以,无论A拿走多少个,B都能够一次拿走剩余的物品,B取胜。因此我们发现了如何取胜的法则:如果,(X为任意自然数,Y≤K),那么A要拿走Y个物品,如果B拿走T(T≤K)个,那么A再拿走个,结果剩下个,以后保持这样的取法,那么A肯定获胜。总之,要保持给对手留下的倍数,就能最后获胜。
这个游戏还可以有一种变相的玩法:两个人轮流报数,每次至少报一个,最多报十个,谁能报到100者胜。
include<stdio.h>
int main()
{
long long N,K;
int t;
scanf("%d",&t);
while(t--){
scanf("%lld%lld",&N,&K);
if(N % (K + 1))
printf("A\n");
else
printf("B\n");
}
}
]]>康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
]]>康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。
其中,为整数,并且
n位(0~n-1)全排列后,其康托展开唯一且最大约为n!,因此可以由更小的空间来储存这些排列。由公式可将X逆推出唯一的一个排列。
对于一个有n个不同元素的集合的从小到大排序(从大到小 同理)的全排列 显然它有项。如n=4,那么就有项。
与自然数与之一一对应。比如 四个数的全排列按字典序如下:
1234:第一个 | 2134:第七个 | 3124:第13个 | 4123:第19个 |
---|---|---|---|
1243:第二个 | 2143:第八个 | 3142:第14个 | 4132:第20个 |
1324:第三个 | 2314:第九个 | 3214:第15个 | 4213:第21个 |
1342:第四个 | 2341:第十个 | 3241:第16个 | 4231:第22个 |
1423:第五个 | 2413:第11个 | 3412:第17个 | 4312:第23个 |
1432:第六个 | 2431:第12个 | 3421:第18个 | 4321:第24个 |
问:求4132是第几个排列?
解:总共4个数,所以n=4.ans:=0;
第一个数是4,研究比4小的并且还没有出现过的数有3个:1,2,3。
其中,a4 = 3 ,那么ans:=ans+3*(n-1)!所以 ans:= ans+ 3*(4-1)! =18
第二个数是1,研究比1小的并且还没有出现过的数为 0个。
其中,a3 = 0 ,那么ans:=ans+0 * (n-2)!,那么ans不变。
第三个数是3,研究比3小的并且还没有出现过的数为1个:1,2。
其中,a2 = 2 ,那么ans:=ans+ 1* (n-3)!,那么ans:=18+1* (4-3)!=19
第四个数是2,研究比2小的并且还没有出现过的数为0个。
其中,a1 = 0 ,那么ans不变。
最后ans怎么等于19啊??代表它前面有19个排列嘛,那么4132自己就是第20个罗( 最后ans:=ans+1)
**例:**1~5从小到大全排列中,找出第96个排列?
int fac[] = {1,1,2,6,24,120,720,5040,40320}; //阶乘
//康托展开
int kt(int n,int s[]){
int sum = 0,smallNum;
for(int i=0; i < n; i++){
smallNum = 0; //比当前数小的数
for(int j=i+1; j<n; j++)
if(s[i] > s[j]) smallNum++;
sum += smallNum * fac[n-i-1];
}
return sum;
}
//康托逆展开
void invKT(int n, int k, int s[]){
int t,j;
bool visit[10] = {false}; //需要记录该数是否已在前面出现过
for(int i=0; i<n; i++){
t = k/fac[n-i-1];
for(j=1; j<=n; j++){
if(!visit[j]){
if(t == 0) break;
t--;
}
}
s[i] = j;
visit[j] = true;
k %= fac[n-i-1];
}
}
]]>
- 把多于n+k个的物体放到n个抽屉里,则至少有一个抽屉里的东西不少于两件。
- 把多于mn(m乘以n)+1(n不为0)个的物体放到n个抽屉里,则至少有一个抽屉里有不少于(m+1)的物体。
- 把无穷多件物体放入n个抽屉,则至少有一个抽屉里 有无穷个物体。
]]>
- 把个物体放入n个抽屉中,其中必有一个抽屉中至多有个物体 (例如,将3×5-1=14个物体放入5个抽屉中,则必定有一个抽屉中的物体数少于等于3-1=2)。
- 把多于n+k个的物体放到n个抽屉里,则至少有一个抽屉里的东西不少于两件。
- 把多于mn(m乘以n)+1(n不为0)个的物体放到n个抽屉里,则至少有一个抽屉里有不少于(m+1)的物体。
- 把无穷多件物体放入n个抽屉,则至少有一个抽屉里 有无穷个物体。
- 把个物体放入n个抽屉中,其中必有一个抽屉中至多有个物体 (例如,将3×5-1=14个物体放入5个抽屉中,则必定有一个抽屉中的物体数少于等于3-1=2)。
HOHO,终于从Speakless手上赢走了所有的糖果,是Gardon吃糖果时有个特殊的癖好,就是不喜欢将一样的糖果放在一起吃,喜欢先吃一种,下一次吃另一种,这样;可是Gardon不知道是否存在一种吃糖果的顺序使得他能把所有糖果都吃完?请你写个程序帮忙计算一下。
第一行有一个整数T,接下来T组数据,每组数据占2行,第一行是一个整数N(0<N<=1000000),第二行是N个数,表示N种糖果的数目Mi(0<Mi<=1000000)。
对于每组数据,输出一行,包含一个"Yes"或者"No"。
2
3
4 1 1
5
5 4 3 2 1
No
Yes
设其中某类糖果的数量最多,数量为max,总的糖果数为sum,如果mam>sum-max+1;则一定不能吃完,否则能吃完。
#include <stdio.h>
int main()
{
__int64 n,m,t,max,sum;
scanf("%I64d",&t);
while(t--){
sum=0;
max=0;
scanf("%I64d",&n);
for(int i=0;i<n;i++){
scanf("%I64d",&m);
sum+=m;
if(max<m)
max=m;
}
sum-=max;
if(max>sum+1)
printf("No\n");
else
printf("Yes\n");
}
return 0;
}
Every year there is the same problem at Halloween: Each neighbour is only willing to give a certain total number of sweets on that day, no matter how many children call on him, so it may happen that a child will get nothing if it is too late. To avoid conflicts, the children have decided they will put all sweets together and then divide them evenly among themselves. From last year's experience of Halloween they know how many sweets they get from each neighbour. Since they care more about justice than about the number of sweets they get, they want to select a subset of the neighbours to visit, so that in sharing every child receives the same number of sweets. They will not be satisfied if they have any sweets left which cannot be divided.
Your job is to help the children and present a solution.
The input contains several test cases.
The first line of each test case contains two integers c and n (1 ≤ c ≤ n ≤ 100000), the number of children and the number of neighbours, respectively. The next line contains n space separated integers a1 , ... , an (1 ≤ ai ≤ 100000 ), where ai represents the number of sweets the children get if they visit neighbour i.
The last test case is followed by two zeros.
For each test case output one line with the indices of the neighbours the children should select (here, index i corresponds to neighbour i who gives a total number of ai sweets). If there is no solution where each child gets at least one sweet, print "no sweets" instead. Note that if there are several solutions where each child gets at least one sweet, you may print any of them.
4 5
1 2 3 7 5
3 6
7 11 2 5 13 17
0 0
3 5
2 3 4
有c个孩子,去n个邻居家要糖果,现在已知每个邻居所能给的糖果数ai,问怎么个要法能保证全部所得的糖果能被c个孩子平分。
我们考虑前k个邻居的糖果总数,那么这样的和一共有n个,a1,a1+a2,...,a1+...+an,如果将他们分别除以孩子的个数c,那么余数只可能在1~c-1之间,换句话说,和有n个,而余数有c-1个,并且从题目中可知c<=n,那么根据抽屉原理可知,必然有两个和的余数是相同的,这也就意味着这两个和中包含的公有项的和能被c整除,即为所求答案。
include <cstdio>
include <cstring>
const int MAXN = 1e5+5;
int a[MAXN], SumMod[MAXN], flag[MAXN];
int main()
{
int c, n;
while(~scanf("%d%d", &c, &n) && (c+n))
{
SumMod[0] = 0;
memset(flag, 0, sizeof(flag));
for(int i=1; i<=n; ++i)
scanf("%d", &a[i]);
for(int i=1; i<=n; ++i)
{
SumMod[i] = (SumMod[i-1] + a[i]) % c; // 处理前缀和,并取模
if(flag[SumMod[i]] == 0)
flag[SumMod[i]] = i;
else // 如果相同的余数在前面出现过
{
int j = flag[SumMod[i]] + 1;
int k = j;
for(; j<=i; ++j) // 公共项即为答案
if(j == k) printf("%d", j);
else printf(" %d", j);
break;
}
}
puts("");
}
return 0;
}
]]>一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的 值。例如:3*3的矩阵:
-1 3 -1
2 -1 3
-3 1 2
和最大的子矩阵是:
3 -1
-1 3
1 2
一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的 值。例如:3*3的矩阵:
-1 3 -1
2 -1 3
-3 1 2
和最大的子矩阵是:
3 -1
-1 3
1 2
第1行:M和N,中间用空格隔开(2 <= M,N <= 500)。
第2 - N + 1行:矩阵中的元素,每行M个数,中间用空格隔开。(-10^9 <= M[i] <= 10^9)
输出和的最大值。如果所有数都是负数,就输出0。
3 3
-1 3 -1
2 -1 3
-3 1 2
7
]]>对于正整数 a, b,存在s,t∈Z 使 sa + t b = gcd (a,b) 成立。
对于正整数 a, b,存在s,t∈Z 使 sa + t b = gcd (a,b) 成立。
正整数a,b∈互质当且仅当存在整数s,t∈$Z $满足 sa+tb=1。
1. 已知整数 和 互质,那么,所以存在整数 使得 ;
2. 假设存在一个整数 dd 是正整数 aa 和正整数 bb 的公共因子,则$ d\mid a$且 ;
3. 因为 是 $a $的因数,且 是整数,所以 也一定是 的因数,也就是说 ;
4. 因为 是 的因数,且 是整数,所以 也一定是 的因数,也就是说 ;
5. 存在整数 使得我们可以将 表示为 ,将 表示为 ;所以,我们可以得 ;由此我们可以知道 ;
6. 因为我们已知 ,所以 。由此,我们只能是 或 。所以 和 的公共正整数因子只有 1,所以 互质;
7. 综上可得“正整数 。
Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。并且能一键部署到GitHub Pages。
首先,不论这篇文章主要是为了安装什么,第一步该做的是先说明一遍大致的过程,以使读者能够清楚,自己究竟在干什么、还有什么没有完成、为什么要这么做。当然,我会给出一些必要的网站,它们以官网为主,庆幸的是,这些官网都有简体中文的支持。最后,说明一下作者的系统为Mint Linux,它和Ubuntu是一样的,并且作者是一个大二菜鸟,如果有错的话,希望大家能够指出错误,我也会立即改正。
很不幸运的是官方给出的 Node.js 安装方法并不是非常有效。因此我通过Baidu找到了一种简单的方法,在此给出过程。
下载
第一步很简单,就是从官网上下载二进制包。给出地址。
解压下载好的 node-v6.9.2-linux-x64.tar.xz 压缩包
$ tar xvf node-v6.9.2-linux-x64.tar.xz
这样,你可以得到一个名为 node-v6.9.2-linux-x64 的文件夹。
验证 Node.js 的版本
首先进入 node-v6.9.2-linux-x64 文件夹下的 bin 目录,你会发现有两个可执行文件。如下:
$ cd node-v6.9.2-linux-x64/bin
$ ls
node npm
接着我们来看看 Node.js 的版本
$ ./node -v
v6.9.2
很好,它是最新的6.9.2版本。
把二进制包放到较为规范的地方。
什么叫较为规范的地方?举个例子,在Windows下,排除自己定义安装路径的软件,你所有的软件都会在这样一个地址下 C:\Program Files
。在Mint Linux上,我把它规定为 /opt
,这个路径包含了所有我手动安装的软件,毕竟虽然有 apt ,但是总有些软件不能通过apt安装。很好,下面让我们把它挪到那个规范的地方。
$ sudo mv node-v6.9.2-linux-x64 /opt/
$ cd /opt
$ ls
clion eclipse google node-v6.9.2-linux-x64 pycharm sublime_text
由此你可以发现,我已经成功移动了文件。这里有个小问题,在执行第一句命令的时候,会提示需要密码,不要担心,直接输入root密码就行,它不是明文的,并不会显示字符。
建立软链接,设置全局
怎么在shell中直接访问呢?就是通过软链接实现。
$ sudo ln -s /opt/node-v6.9.2-linux-x64/bin/node /usr/local/bin/node
$ sudo ln -s /opt/node-v6.9.2-linux-x64/bin/npm /usr/local/bin/npm
$ cd /usr/local/bin/
$ ls |grep '^[n]'
node
npm
你会发现,在 /usr/local/bin
这个目录下已经有 node 、npm 两个文件了。
验证成功
打开terminal,输入node -v
和npm -v
来检查是否成功。
$ node -v
v6.9.2
$ npm -v
3.10.9
由此Node.js安装完成,看似很复杂,其实很简单。
Git的安装是通过apt,极其便捷。
sudo apt-get install git
这样就安装完了。这就是包管理的好处。
其次,ssh的配置安装则参考Git教程 - 廖雪峰的官方网站 ,这是非常好的git教程网站。
所有必备的应用程序安装完成后,即可使用 npm 安装 Hexo。根据官方教程:
$ npm install -g hexo-cli
如果出现WARNING,那么你可以忽视它。如果出现ERROR,那么请你使用Baidu或者Bing来解决问题,作者病不能,开速有效的替你解决。
首先,我打算把博客的根地址定在 ~/Document/
。那么开始
$ cd ~/Documents/
$ hexo init Blog
$ cd Blog
$ npm install
新建完成后,指定文件夹的目录如下:
.
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes
注意,这里有一些问题。执行第二个命令时常常由于网络的问题卡住,那么建议你把npm的源改为淘宝源,具体教程查看npm设置淘宝镜像
您可以在 _config.yml
中修改大部份的配置。当然参考官方文档以获得最好的支持
当然你可以查看我的修改方法。
参数 | 描述 | 我的配置 |
---|---|---|
title | 网站标题 | Francis'Blog |
subtitle | 网站副标题 | |
description | 网站描述 | |
author | 作者的名字 | Andy Francis |
language | 网站使用的语言 | zh-CN |
timezone | 网站时区 | Asia/Shanghai |
参数 | 描述 | 我的配置 |
---|---|---|
url |
网址 | https://dongfrancis.github.io/ |
root |
网站根目录 | / |
permalink |
文章的 永久链接 格式 | :year/:month/:day/:title/ |
permalink_default |
永久链接中各部分的默认值 |
参数 | 描述 | 我的配置 |
---|---|---|
source_dir |
资源文件夹,这个文件夹用来存放内容。 | source |
public_dir |
公共文件夹,这个文件夹用于存放生成的站点文件。 | public |
tag_dir |
标签文件夹 | tags |
archive_dir |
归档文件夹 | archives |
category_dir |
分类文件夹 | categories |
code_dir |
Include code 文件夹 | downloads/code |
i18n_dir |
国际化(i18n)文件夹 | :lang |
skip_render |
跳过指定文件的渲染,您可使用 glob 表达式来匹配路径。 |
参数 | 描述 | 我的配置 |
---|---|---|
new_post_name |
新文章的文件名称 | :title.md |
default_layout |
预设布局 | post |
auto_spacing |
在中文和英文之间加入空格 | false |
titlecase |
把标题转换为 title case | false |
external_link |
在新标签中打开链接 | true |
filename_case |
把文件名称转换为 (1) 小写或 (2) 大写 | 0 |
render_drafts |
显示草稿 | false |
post_asset_folder |
启动 Asset 文件夹 | false |
relative_link |
把链接改为与根目录的相对位址 | false |
future |
显示未来的文章 | true |
highlight |
代码块的设置 |
参数 | 描述 | 我的配置 |
---|---|---|
default_category |
默认分类 | uncategorized |
category_map |
分类别名 | |
tag_map |
标签别名 |
Hexo 使用 Moment.js 来解析和显示时间。
参数 | 描述 | 我的配置 |
---|---|---|
date_format |
日期格式 | YYYY-MM-DD |
time_format |
时间格式 | H:mm:ss |
参数 | 描述 | 我的配置 |
---|---|---|
per_page |
每页显示的文章量 (0 = 关闭分页功能) | 10 |
pagination_dir |
分页目录 | page |
参数 | 描述 | 我的配置 |
---|---|---|
theme |
当前主题名称。值为false 时禁用主题 |
material |
此theme的配置默认为landscape,我这里的material为其它主题。
deploy:
type: git
repo: git@github.com:DongFrancis/DongFrancis.github.io.git
branch: master
如果以上内容你已经完成那么我们可以试着在本地测试一下,首先你必须进入博客的根目录,其次启动服务。想这样:
$ cd ~/Documents/Blog
$ hexo generate
$ hexo server
然后打开浏览器,进入地址 http://localhost:4000/ ,你会发现你的个人博客已经搭建完成!!!
如果你想同步到Github Pages,确保你已经完成了 git的安装与配置、 git的ssh设置、 Github Pages的申请与建立、Deployment的配置 。
很好,现在我们可以继续了。
进入Blog根目录,执行如下操作
$ npm install hexo-deployer-git --save
在Blog根目录,执行如下操作
$ hexo deploy
打开Github pages的个人主页,如 https://DongFrancis.github.io.git,你可以验证是否同步成功。
在Blog根目录下,你可以使用 new
命令来新建文章。如
$ hexo new "my-first-blog"
执行完此命令后,在source/_posts/
目录下会有一个新的文件 my-first-blog.md
。
毫无疑问,对于自己些的文章,你总希望确认一下是否完美,这样你才可以展示给其它人看。
具体这样来完成:
$ hexo generate
$ hexo server
很熟悉的俩句话是么?没错,这就是本地测试的俩个命令,第一句的意思是生成文件,第二局的意思是打开本地服务器 。
很简单使用 deploy 命令即可。
$ hexo deploy
执行完命令后,你便可以在Github Pages上查看了。
我选择的是 Meterial 主题,一句话:好看!
具体的过程和官网的教程一样这里就不详细讲了。
搭建博客的作用对于不同的人有不同的作用。对于我来说,是希望将自己所学的知识进一步整理与归纳,以此逐步提升自己。希望这篇教程对大家有所帮助。
]]>