Yet it also might be worth asking: what are the motivations of these “perfect” behaviors? Are they always desirable?
One important aspect of the “perfect” collection behavior is the magical uniform return type principle. Sloppily speaking, this means that transforming a collection should give you another collection of the same kind. For instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Clean and consistent, can’t be more intuitive. Of course filtering a List should return a List, and mapping a Set should give you a Set. Both compile time and runtime. Works even for non-collections like Strings.
When I first saw this, I thought “yeah, cool”, and then forgot about it. I took it for granted as just another Scala magic that makes my life easier, without realizing its intricate implications. Not until recently have I realized that Scala’s collections were much smarter, or trickier, than I had expected.
The uniform return type principle is fairly straightforward for operations like filter, take, drop and slice,
but situations get complicated in the cases of some other operations like map and flatMap. Consider the following:
1 2 | |
It starts getting interesting. When mapping a Set, you should get a Set, which by definition doesn’t contain
repeated values. So you get a Set[Boolean] which consists of only 2 values. You might get surprised here if
you are familiar with LINQ in C#. In C#
1
| |
gives you an IEnumerable that consists of “false, true, false, true” in order, because the HashSet is simply
iterated through as a Normal IEnumerable. Some say C#/LINQ sucks, because the type information of original collection is “lost”.
In Scala, Map[K, V] is a subtype of Iterable[(K, V)], thus can be seen as a collection of key-value pairs.
Now stop for a moment and think about it: what happens if you map a Map?
1 2 3 4 5 | |
Ideally, according to the uniform return type principle, mapping a Map should give you another Map.
But this is only possible when the target element type is key-value pair. Otherwise, the best we can
do is to treat the Map as just an Iterable of key-value pairs and return an List of the target elements.
However, there is still the problem of inconsistency. Here’s an example:
1 2 3 4 5 | |
Same operation on two Sets, but the behaviors are totally different, simply because these two Sets have
different types of values? This is weird. The problem is that 2-tuples are selected to represent the key-value pairs,
but are not exclusively used for this purpose. So when user map the elements to 2-tuples, there’s this unavoidable ambiguity.
It becomes even more error-prone if the actual class type of the collection is not known at compile time.
We know that Map has a property “keys” that gives you an Iterable of its keys. Consider the following example:
1 2 3 4 5 | |
Ah…the runtime type of “keys” is Set[Int], so repeated values in the returned collection are also ignored.
The behavior should surprise you if you haven’t think of this. In fact, when given a Iterable without knowing its actual type,
in any case the behaviors of collection operations are simply unpredictable. Scala guys can argue that this is just how it works,
but this doesn’t seems to be a good thing, given that Iterables have been widely used between interfaces to pass data collections
around without specifying their internal structures.
To sum up, the behavior of the map operation depends on not only the type of the collection on which it invokes, both static and dynamic, but also on the type of the mapped elements.
How is this magic implemented? If you take a look at the source code, you will probably see the most complicated collection library you’ve ever seen in your life. I’ll try to give a brief and simplified explanation. Here is the actual signature of the map method:
1 2 | |
As you can see there’s an extra implicit parameter bf of type
CanBuildFrom[This, MappedElem, That], which will give you a Builder[MappedElem, That] that can build a collection (of type That)
from the mapped elements (of type MappedElem). In short, CanBuildFrom[This, MappedElem, That] is a factory for Builder[MappedElem, That],
which itself is a factory for That. When both type parameters This and MappedElem are given, the compiler can find the best eligible
for bf (according to some
perplexing
rules) and consequently determines the static type of That. For example:
1 2 3 4 5 | |
The CanBuildFrom can forward the call to the genericBuilder[MappedElem] method of the collection inferred in compile time,
so that the “right” runtime type can be selected via virtual dispatch.
Now you should have noticed the conceptual and implementation complexity brought by the seemingly simple “uniform return type principle”. But why do we need this at the first place?
Let’s digress to talk about LINQ for a moment. I don’t think the argument “LINQ sucks, because the type information of original collection is lost”
really stands, because I think this is exactly what it was designed to ignore. For most of the times, we often don’t care about whether the Iterable
is a Seq or a Set or a Map or whatever. What we do with a collection is often just querying and consuming its content.
LINQ provides a uniform interface with consistent, lazy behavior to facilitate this. When you need a collection of certain type,
you explicitly convert the query result to it. When you need special behaviors of a Set or Map (Dictionary), you use their own interfaces.
But queries are queries, different collections are treated equally. Such simplicity is not a weakness, but a feature.
In my point of view, LINQ and Scala’s collection operation are cosmetically similar but semantically different. LINQ queries a collection; Scala’s collection operation transforms a collection. (This may give you the clue why LINQ doesn’t have a ForEach extension method.) Scala’s functional nature necessitates the collection transformation semantic: you need to be able to deal with the value of a collection and compute a new value from it. And since Scala is also statically and strongly typed, the “uniform return type principle” is required to make the system consistent. Thus the behaviors of collection operations should of course depend on the actual collection type and the type of the target element. The operations are non-lazy (strict) by default to avoid unintended delay of the side-affects within the operations (there’s a more detailed explanation about the decision on laziness at the bottom of this page).
What if we only want to query and consume the content of the collection, like we do in LINQ? I suggest using Iterator directly or convert the collection to Stream:
1 2 3 4 | |
What comes with the complexity is great flexibility and power. Scala is expressive and seems quite easy, but the paradox is that you need to spend much time learning to avoid misusing it. Scala is not easy, even in the case of these basic collection operations; it’s important to recognize that.
]]>Logistic 回归解决的并不是回归问题,而是分类问题,即目标变量(target variable)的值是离散而非连续的。 这时目标变量也可称为标签(label)。如果仍用线性回归硬搞,得到的结果会非常不靠谱。
我们先考虑简单的情况:数据点只有 0 和 1 两个标签(binary classification),即 , 且大致上是线性可分的(linearly separable)。如图:

那么现在问题就是要找到一个 ,使得直线 1 能够将上面图中的 positive 和 negative 两类数据点“分开”;这样的一条直线称为决策边界(decision boundary)。 ——但具体什么叫“分开”?或者说,如果已知 ,对于一个标签未知的数据点 ,怎么判断它是 positive 还是 negative?
直观上看,在给定 和 的情况下, 应该满足一个0-1 分布,即
其中 应该满足:
那 应该取什么样的函数呢?我们知道 logistic 函数 恰巧满足上述条件, 不妨就取它(“logistic 回归”也因此得名)2。即:
logistic 函数的图像是:

可以直观地看到它确实是满足我们要求的。这样我们就得到了:
我们记 ,表示这是我们希望预测的量,也就是模型的假设(hypothesis)3。在实际分类应用中, 时我们可以给出判断 , 时 , 的话就蒙一个吧。
接下来 该怎么求呢?根据log极大似然法,可知 的最优值就是 ,其中
其中第二步有点小 tricky。注意到 在 时值为 ,在 时值为 。这样就把 两种取值的情况合并写在一个式子里了。
接下来我们可以求出梯度 。如果我们像上篇笔记中一样定义:
可以推导得到:
接下来,根据梯度上升算法 4,我们就可以迭代计算下式来逼近 :
注意到这个更新式的形式和之前线性规划+最小二乘法+梯度下降得到是一样的,只是 变了。
以上就是用梯度上升做 Logistic 回归的算法了。课上还谈到了另外 3 个问题:
p.s. 今天早睡的目标又达成失败了……………………
]]>线性回归解决的是一个回归问题(怎么像废话),即要预测的目标变量(target variable)是连续值。我们有一个大小为 的训练集, 其中数据点 分别对应目标变量 ;每个 是一个 维向量, 为一个数据点的属性(feature)数。(为后面书写简便我们在 个属性前再加一维 ,即 。)给定这样一个训练集,我们希望知道对于某个训练集外的 , 它对应的 是什么。
线性回归的假设(hypothesis)是,数据点的属性 和对应的目标变量 是线性关系,可以用一条直线 来拟合1,就像下面这样(图片盗自维基):

其中 也是一个 维向量,就是我们想拟合的参数,因为假如能求出 , 对于一个训练集外的点 我们就能预测它对应的 了。
为了评价我们估计的 的优劣,我们需要引入一个目标函数/费用函数(cost function)。 根据最小二乘法,我们定义下面的 为我们需要最小化的目标函数:
其中 这个常系数并非必须,只是为了计算便利加上去的。我们要求的就是 。
不过在继续之前我们先把 的记法再进一步 vectorize 一下。我们定义一个 的设计矩阵 ,其第 行为 ,即
我们再定义
不难推出 可以写成下面的形式:
我们知道函数在极值处梯度为零,因此第一种解法就是直接求 。这样可以得到一个解析解:
注意其中 是一个 的矩阵,而矩阵求逆的的复杂度一般是 。 所以这种解法在 比较小的时候非常高效,但在 较大时就瞎了。
我们还知道 上某一点的梯度向量指向 增长最快的方向,长度为这个增长的变化率。 因此第二种解法就是让 的值向着 的方向,即 减小最快的方向迭代变化,这样就可以逼近 的极小值了。因此我们可以得到下面被称为 梯度下降的算法:
Repeat until converge { $$ \begin{eqnarray} \theta_{t+1} &:=& \theta_t - \alpha \nabla_\theta J(\theta) \ &=& \theta_t - \frac{\alpha}{m} X^{\rm T} (\vec{h_\theta} - \vec{y}) \end{eqnarray} $$ }
其中 称为学习速率(learning rate),意思就是……学习的速率= = 这个参数控制着每次迭代 值变化的保守或激进程度; 如果太大,一次迭代对 改变过大,导致“冲得太猛”越过了极值点没法收敛;如果太小,一次迭代对 改变太少,算法收敛太慢。 决定 的方法是……呃,就是试,“0.01 太小,0.1 太大,0.03 有点小,咦 0.06 正好”这样。
对于线性回归和梯度下降,课上还谈到了另外两个话题。一是属性缩放(feature scaling)。如果不同属性数量级差得太大, 会严重影响梯度下降的性能。这时可以对分别对每项属性做类似“减去均值,除以标准差”这样的缩放。二是 多项式回归(polynomial regression)。 属性值和目标变量值之间的关系可能不是线性的,而是多项式的关系,比如 这样。 但由于这个式子对于 仍然是线性的,所以用最小二乘法时可以直接把 、 看成是彼此独立的不同的属性, 然后按上面的方法如法炮制。至于多项式回归时属性的次数怎么取,应该会在讲 bias-variance trade-off 的时候讲吧,我到时候再写好了。
这里讲得有点 sloppy 了(不过你能明白我的意思就行=.=)。更严格一点说,应该是训练集中的数据满足:
其中 表示误差;各 满足均值为零,方差相同且互不相关 (参见高斯-马尔科夫定理)。
顺带一提,我们如果进一步假设 ,有 ,则这个模型成为 广义线性模式(Generalized linear model) 的一个特例。我们用极大log似然法
可以推出和下面提到的最小二乘法一样的形式。——如果你不知道我上面在说什么,忽略吧,不碍得的。↩
推导其实挺麻烦的。教授在课上没讲,我也懒得敲了=.=↩
注意这里每次迭代的运算都会考虑整个训练集,这称为批量梯度下降(batch gradient descent), 当训练集很大时这可能导致很大的开销。我们也可以每次迭代只依次选取训练集中的一个实例 ,更新
这个叫增量梯度下降(incremental gradient descent)。好处在于每次迭代开销小,因而收敛速度较快, 在处理大数据集时可能很有用。但坏处在于这算法可能永远都收敛不到极小值,而是一直围着极小值转——不过在实际应用中这个也能接受了。↩
我们有时会希望在数量庞大的文档库中自动地发现某些结构。比如我们希望在文档库发现若干个“主题”,并将每个主题用关键词的形式表现出来。 我们还希望知道每篇文章中各个主题占得比重如何,并据此判断两篇文章的相关程度。而 pLSA 就能完成这样的任务。
我之前取了 Wikinews 中的 1000 篇新闻,试着用 pLSA 在其中发现 K=15 个主题。比如一篇关于 Wikileaks 的阿萨奇被保释消息的新闻,算法以 100% 的概率把它分给了主题 9,其关键词为:
media phone hacking wikileaks assange australian stated information investigation murdoch
可以看到这个主题的发现还是非常靠谱的。又比如这条中国人民的老朋友威胁要大打打核战争的新闻。 算法把它以 97.7% 的概率分给了主题 3,2.3% 的概率分给了主题 7。主题 3 的关键词是:
south north court china military death tornado service million storm
主题 7 的关键词是:
nuclear plant power japan million carbon radiation china water minister
可以看到这条新闻和主题 3 中的“南北”、“军事”、“中国”、“死亡”这些信息联系在一起,和主题 7 中的“核”、“中国”联系在一起。 应该是因为我的数据集中与北朝鲜核问题相关的新闻只有不到 10 条,而 10 个主题的划分并不够细致,所以关于“朝核问题”或者“核武器”的这样的主题并没能被分离出来。但可以看到即使是这样结果也是很 make sense 的。
那我们就来看看 pLSA 模型是怎么回事吧。和很多模型一样,pLSA 遵从 bag-of-words 假设, 即只考虑一篇文档中单词出现的次数,忽略单词的先后次序关系,且每个单词的出现都是彼此独立的。 这样一来,我们观察到的其实就是每个单词 在每篇文档 中出现的次数 。 pLSA 还假设对于每对出现的 都对应着一个表示“主题”的隐藏变量 。 pLSA 是一个生成模型,它假设 、 和 之间的关系用贝叶斯网络表示是这样的(从 (Blei et al., 2003) 偷的图):

实心的节点 和 表示我们能观察到的文档和单词,空心的 表示我们观察不到的隐藏变量,用来表示隐含的主题。图中用了所谓的“盘子记法”, 即用方框表示随机变量的重复。这里方框右下角的字母 和 分别表示有 篇文档,第 篇文档有 个单词。每条有向边表示随机变量间的依赖关系。也就是说,pLSA 假设每对 都是由下面的过程产生的:
而我们感兴趣的正是其中的 和 :利用前者我们可以知道每篇文章中各主题所占的比重, 利用后者我们则能知道各单词在各主题中出现的概率,从而进一步找出各主题的“关键词”。记 , 表示我们希望估计的模型参数。当然 不仅仅代表两个数,而是对于每对 和 , 我们都要希望知道 和 的值。也就是说,模型中共有 个参数。我们还知道:
根据最大log似然估计法,我们要求的就是
这里由于 这一项与 无关,在 中可以被直接扔掉。1因此
这里出现了 套 的形式,导致很难直接拿它做最大似然。但假如能观察到 ,问题就很简单了。于是我们想到根据 EM 算法 (参见我的上篇笔记),可以用下式迭代逼近 :
其中
在 E-step 中,我们需要求出 中除 外的其它未知量,也就是说对于每组 我们都需要求出 。 根据贝叶斯定理贝叶斯定理,我们知道:
而 和 就是上轮迭代求出的 。这样就完成了 E-step。
接下来 M-step 就是要求 了。利用基本的微积分工具 2,可以分别对每对 和 求出:
以上就是 pLSA 算法了。最后贴个我用 MATLAB 写的实现 3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
第一次拿 Mablab 写程序,比较丑……4下图是 Log 似然度随迭代收敛的情况。可以看到收敛速度还是相对较快的。 而且由于是 EM 算法的缘故,Log 似然度确实是单调上升的.

最后,pLSA 的问题是在文档的层面上没有一个概率模型,每篇文档的 P(d|z) 都是需要拟合的模型参数。 这就导致参数的数目会随文档数目线性增长、不能处理训练集外的文档这样的问题。所以02 David Blei、Andrew Ng(就是正在 ml-class.org 里上公开课的那位) 和 Michael Jordan 又提出了一个更为简洁的模型:LDA。有时间的话下次再写了。
参考文献
这里 Hofmann 自己在 (Hofmann, 1999) 和 (Gildea & Hofmann, 1999) 中使用了不同的形式。本文和 (Gildea & Hofmann, 1999)、(Brants, 2005) 一样选择不去理会 。因为正如 (Brants, 2005) 中指出、(Blei et al., 2003) 及很多其它文献吐槽的那样,(Hofmann, 1999) 中的模型算出的 实在坑爹,当 不在训练集中时 就一律为0,没什么意义,还不如别估计它呢。另外 (Hofmann, 1999) 中额外引入了一个参数 来“解决”过度拟合问题,但 (Brants, 2005) 中指出这一问题实际并不存在,因此本文也对此忽略不提。↩
具体而言,这里要求的是 在 和 约束条件下的极值。根据拉格朗日乘数法,解: $$ \nabla_\theta \left( Q(\theta) + \sum_z \alpha_z (\sum_w P(w|z) -1) + \sum_d \beta_d (\sum_z P(z|d) -1) \right) = \mathbf{0} $$↩
吐槽:用 Matlab 做简单字符串处理怎么都那么恶心!长度不同的字符串竟然算是不同类型的!Cell array 怎么那么难用!↩
Expectation-Maximization 算法是统计学中用来给带隐含变量的模型做最大似然(和最大后验概率)的一种方法。EM 的应用特别广泛,经典的比如做概率密度估计用的 Gaussian Mixture Model。这两天我或许还会写 p 的笔记放上来,也是 EM 应用的例子。
下面我会先解释 EM 算法要解决的问题,然后直接给出算法的过程,最后再说明算法的正确性。
首先我们定义要解决的问题。给定一个训练集 ,我们希望拟合包含隐含变量 的模型 中的参数 。根据模型的假设,每个我们观察到的 还对应着一个我们观察不到的隐含变量 ,我们记 。做最大对数似然就是要求 的“最优值”:
其中
想用这个套的形式直接求解 往往非常困难。而如果能观察到隐含变量 ,求下面的似然函数的极大值会容易许多:
问题是实际上我们没法观察到 的值,只能在给定 时求 的后验概率 。1 EM 算法就可以帮我们打破这样的困境。
下面给出 EM 算法的过程。其中 是第 t-1 次迭代时算出的 值; 为任意初值。
Repeat until converge {}
- (E-step) Calculate \( P(Z|X;\theta_t) \), in order to get: $$ \begin{eqnarray} E_{Z|X;\theta_t}[L(\theta;X,Z)] &:=& E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}] \ &=& \sum_Z P(Z|X;\theta_t) \log{P(X,Z;\theta)} \end{eqnarray} $$
- (M-step) $$\theta_{t+1} := \arg\max_\theta E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}]$$
对,就这么短。所以我总觉得称之为 algorithm 不如称之为 method 更恰当。上面的过程在收敛后就得到了我们需要的 2。
先简单说说这短短两步都做了些啥。EM 算法每次迭代都建立在上轮迭代对 的最优值的估计 上,利用它可以求出 的后验概率 ,进而求出 在分布 上的期望 。在第一节中我们提到 在未知 的情况下难以直接计算,于是 EM 算法就转而通过最大化它的期望 来逼近 的最优值,得到 。注意由于 的这个期望是在 的一个分布上求的,这样得到的表达式就只剩下 一个未知量,因而绕过了 未知的问题。而 又可以作为下轮迭代的基础,继续向最优逼近。算法中 E-step 就是在利用 求期望 ,这就是所谓“Expectation”;M-step 就是通过寻找 最大化这个期望来逼近 的最优值,这就叫“Maximization”。EM 算法因此得名。
另外,如果数据满足独立同分布的条件,分别枚举 的值可能要比枚举整个 方便些,可把 替换成:
为什么这样 E一步,M一步,一步E,一步M,就能逼近极大似然?具体而言,为什么通过迭代计算 可以逼近 的最优值 ?我们稍后会看到,这是因为每次迭代得到的 一定比 更优,即算法是在对 的最优值做单调逼近。
不过首先让我们先抛开最大似然,考虑一个更一般的问题。假设有一个凹函数 ,我们想求 ,但直接求很困难。不过对于任意给定的 ,假设我们都能找到 的一个下界函数 ,满足 且 ——我管 这样的函数叫 的“在 处相等的下界函数”。现在考虑 ,它一定会满足:
也就是说, 一定比 更优。而接下来我们又可以用 找到一个 ,再据此算出比 还优的 。如此不断迭代,就能不断步步逼近 的最优值了。由此可见,如果对任意 都能找到 的“在 处相等的下界函数”,我们就得到了一个能单调逼近 的最优值的算法:
Repeat until converge {}
- 找到函数 \( F(\theta) \) 的“在 \( \theta_t \) 处相等的下界函数” \( G_{\theta_t}(\theta) \)
- \( \theta_{t+1} := \arg\max_\theta G_{\theta_t}(\theta) \)
上面的算法看起来和 EM 算法的每步都分别对应——事实上也正如此。下面是从 PRML 中偷的一张图改的,展示了上述逼近的过程:

现在我们回到最大似然问题 。如果我们想套用上面的算法来逼近 的这个最优解,就需要找到对于每个 ,函数 的“在 处相等的下界函数”。该怎么找呢?让我们从 的初始形式开始推导:
又卡在这个套的形式上了……我们说过麻烦在于观察不到 的值,那不妨给它任意引入一个概率分布 3,利用分子分母同乘 的小 trick,得到:
根据 Jensen 不等式 4,对于任意分布 都有:
且上面的不等式在 为常数时取等号。
于是我们就得到了 的一个下界函数。我们要想套用上面的算法,还要让这个不等式在 处取等号,这就这要求在 时 为常数,即 。由于 是一个概率分布,必须满足 ,所以这样的 只能是 。那我们就把 代入上式,得到:
且该不等式在 时取到等号。那么…… 就是 的“在 处相等的下界函数”——这不就是我们要找的么!于是把它塞进本节开始得到的算法里替换“”就可以用啦。也就是说,迭代计算 就可以逼近 的最优值了。而由于利用 Jensen 不等式的那一步搞掉了套的形式,它算起来往往要比直接算 容易不少。
我们还可以再做几步推导,得到一个更简单的形式:
其中倒数第二步是因为 这一项与 无关,所以就直接扔掉了。这样就得到了本文第二节 EM 算法中的形式——它就是这么来的。
以上就是 EM 了。至于独立同分布的情况推导也类似。
顺带一提, 有时也比较难算。这时我们其实可以退而求其次,不要求这个期望最大化了,只要它在 处的值大于在 处的值就行了。根据上面的推导,这样也能逼近 的最优值,只是收敛速度较慢。这就是所谓 GEM (Generalized EM) 算法了。
p.s. MathJax 很神嘛。
p.p.s. 这篇笔记竟然断断续续写写改改了两天多,我对 EM 的认识也越来越清晰。“‘教’是最好的‘学’”真是一点没错。
更新历史:
谢谢你,因为这183天中的每时每刻,因为那所有的一切。
别了。
]]>
To the Daisy
by W. Wordsworth
With little here to do or see
Of things that in the great world be,
Sweet Daisy! oft I talk to thee,
For thou art worthy,
Thou unassuming commonplace
Of Nature, with that homely face,
And yet with something of a grace
Which Love makes for thee!
Oft on the dappled turf at ease
I sit and play with similes,
Loose types of things through all degrees,
Thoughts of thy raising;
And many a fond and idle name
I give to thee, for praise or blame,
As is the humour of the game,
While I am gazing.
A nun demure, of lowly port;
Or sprightly maiden, of Love's court,
In thy simplicity the sport
Of all temptations;
A queen in crown of rubies drest;
A starveling in a scanty vest;
Are all, as seems to suit thee best,
Thy appellations.
A little Cyclops, with one eye
Staring to threaten and defy,
That thought comes next—and instantly
The freak is over,
The shape will vanish, and behold!
A silver shield with boss of gold
That spreads itself, some fairy bold
In fight to cover.
I see thee glittering from afar—
And then thou art a pretty star,
Not quite so fair as many are
In heaven above thee!
Yet like a star, with glittering crest,
Self-poised in air thou seem'st to rest;—
May peace come never to his nest
Who shall reprove thee!
Sweet Flower! for by that name at last
When all my reveries are past
I call thee, and to that cleave fast,
Sweet silent creature!
That breath'st with me in sun and air,
Do thou, as thou art wont, repair
My heart with gladness, and a share
Of thy meek nature!

(需要下载才能看到全部内容。层次很深,请多用F6下钻功能)
]]>猛击这里下载。注意,此程序仅支持2010年初的新版新浪博客,之前或之后的版本都不支持。
要运行程序,你需要确保已经安装过JRE。双击运行后显示如图界面,填入自己的博客地址(不要省略“ http:// ”),然后点击“Start”即可。这时“Start”按钮变为灰色,标题栏显示“Extracting”。等待几分钟,当标题栏显示为“Done”、“Start”按钮重新变为可用时,程序所在目录下会出现一个blog.xml文件。把这个文件直接导入WordPress就可以了。
代码也打在jar包里了,MIT协议。欢迎报告bug。
下面是废话。
恩由于新浪用了ajax,评论信息是通过xhr异步读取的,用一般的方法没法抓到。我纠结许久,最后是用了非常ad hoc的方法解决的,不知道有没有什么什么不太麻烦的通用解决方案呢。
再扯两句scala。我都想不起来当初具体是怎么想到要学scala的,也许是为了了解下函数式编程,也许只是想在jvm上有一个喜欢的语言吧——Java写起来太不爽了;Java社区的低效和保守也已经开始显出C++的影子。
scala确实是非常强大和灵活;我在见到一些颇富技巧性的hack之后都有些怀疑scala社区的风气会不会慢慢变得像C++社区一样过分热衷技巧的炫耀。不过scala的设计目标就是以较简单的语法规则获得最大的scalability,不需要通过挖掘语言规范里的犄角旮旯来实现一些必要功能,所以不会像C++一样成为一门本身已相当复杂,却还需要别人反过来教语言发明者如何使用的语言。
scala毕竟表现力比Java强太多,代码也简洁太多。比如这次我需要实现一个抛出异常后重试若干次的逻辑,只需定义一个函数:
1 2 3 4 5 6 7 | |
然后这样使用:
1
| |
程序就会不断获得网页源代码,并在5次失败后抛出异常。Java实现同样的东西可不会如此优雅了。又如下面这段代码返回一篇博文xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
注意,xml标签直接作为scala的源代码的一部分在代码中出现!虽然我觉得这样会使scala语言多出一种“特殊情况”,增加语言的复杂性,但不得不承认这样的设计确实非常优美简洁。
我比较看好scala,以后自己做跑在jvm上的东西scala应该是首选语言。推荐有兴趣的童鞋也了解一下。
]]>
说话的东西和说的话都随机出现。这个效果是我在Linux Mint里面看到的,感觉很有爱。下周考Unix环境编程,周末恶补一下,顺便写这个小脚本练手。
以我在用的 ubuntu 为例。首先确保安装 fortunes 和 cowsay 两个包。前者用于显示各种各样的趣味短句,后者则提供了一头会说话的奶牛(和其它各种诡异的东西)。关于fortunes还有一些有趣的包你可能也想一起安装,比如fortune-zh里有唐诗宋词,fortunes-ubuntu-server则有关于使用Ubuntu Server的贴士,等等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
保存后加上执行权限:
1
| |
然后把这个文件复制到/usr/bin下
1
| |
最后打开/etc/bash.bashrc
1
| |
并在最后加上一行:
1
| |
保存后打开终端,应该就是这个效果了:

ZSoft Corp. is a software company in GaoKe Hall. And the workers in the hall are very hard-working. But the elevator in that hall always drives them crazy. Why? Because there is only one elevator in GaoKe Hall, while there are hundreds of companies in it. Every morning, people must waste a lot of time waiting for the elevator.
Hal, a smart guy in ZSoft, wants to change this situation. He wants to find a way to make the elevator work more effectively. But it’s not an easy job.
There are 31 floors in GaoKe Hall. It takes 4 seconds for the elevator to raise one floor. It means:
It costs (31-1)4=120 seconds if the elevator goes from the 1st floor to the 31st floor without stop. And the elevator stops 10 second once. So, if the elevator stops at each floor, it will cost 304+2910 = 410 seconds (It is not necessary to calculate the stopping time at 31st floor). In another way, it takes 20 seconds for the workers to go up or down one floor. It takes 3020 = 600 seconds for them to walk from the 1st floor to the 31st floor. Obviously, it is not a good idea. So some people choose to use the elevator to get a floor which is the nearest to their office.
After thinking over for a long time, Hal finally found a way to improve this situation. He told the elevator man his idea: First, the elevator man asks the people which floors they want to go. He will then design a stopping plan which minimize the time the last person need to arrive the floor where his office locates. For example, if the elevator is required to stop at the 4th, 5th and 10th floor,the stopping plan would be: the elevator stops at 4th and 10th floor. Because the elevator will arrive 4th floor at 34 = 12 second, then it will stop 10 seconds, then it will arrive 10th floor at 34+10+6*4 = 46 second. People who want to go 4th floor will reach their office at 12 second, people who want to go to 5th floor will reach at 12+20 = 32 second and people who want to go to 10th floor will reach at 46 second. Therefore it takes 46 seconds for the last person to reach his office. It is a good deal for all people.
Now, you are supposed to write a program to help the elevator man to design the stopping plan,which minimize the time the last person needs to arrive at his floor.
The input consists of several testcases. Each testcase is in a single line as the following: n f1 f2 … fn
It means, there are totally n floors at which the elevator need to stop, and n = 0 means no testcases any more. f1 f2 … fn are the floors at which the elevator is to be stopped (n <= 30, 2 <= f1 < f2 … fn <= 31). Every number is separated by a single space.
For each testcase, output the time the last reading person needs in the first line and the stopping floors in the second line. Please note that there is a summary of the floors at the head of the second line. There may be several solutions, any appropriate one is accepted. No extra spaces are allowed.
Asia Guangzhou 2003
很久不更新了,写个水水的解题报告充数。。。这是最近做的算法习题,网上看到这题的题解都是二分+贪心的,这里提供一个dp解法。
理解题意的时候有一点需要特别注意:题目所描述的整个过程是“并行”的。所以所有人都到达各自楼层的用时只与最晚到达的人有关。
首先,由于去各楼层乘客的具体数目对结果没有影响,为表述方便,我们假设每个目标楼层只有一个人要去,并且把各个目标楼层与要去该楼层的那个乘客对应。下面在说“电梯里还剩i个人”的时候,就是在说“电梯里的乘客还要去i个楼层”。
粗略的阶段划分和状态表示还是很简单的。
电梯从第1层开始层层上升,每层都看做一个阶段,任意时刻的状态都可以由“电梯在几楼”和“电梯上都有谁”这两个参数唯一确定。初始状态就是“电梯在1楼”和“所有人都在电梯上”。
在每个阶段需要做出决策,选择让电梯上的哪些人下来自己走(如果没人下来就表示电梯在这层不停)。每个决策发生后,原来电梯里的人被分成两拨:一拨留在电梯上继续上升,另一拨离开电梯开始爬楼锻炼身体。要求某个状态下电梯里的所有人到达各自楼层所需的最短时间,只需要找到一个最优的决策,使得上述两拨人中最晚到达的人尽早到达。
即,若设决策后留在电梯里的人全部到达各自楼层需要时间 T1,离开电梯的人全部到达需要时间 T2,则要求的就是 min{ max(T1, T2) }。其中 T1 可由“当前层数+1”和“决策后剩下的人”确定的状态得到;T2 则是下电梯的人中走的最远的那位所花的时间。
按照上述想法很容易列出转移方程。但是“电梯上都有谁”这一参数有 2^n 种取值,整个算法的复杂度因此能到达令人发指的O(m*2^2n),对于m=31,n=30的数据规模这是不可接受的。
我们需要设法减少需要考虑的状态数目。下面给出两个引理:


以上两点很容易证明。由这两点可以得到一个很简单但足以解决问题的结论:
无论电梯停在哪一层,若要去第 k 层的人选择在这时下电梯,则:所有要去低于k层(第1..k-1层)的人也应选择在此时下电梯,这样一定可以得到当前决策下的一个最优解。如图:

也就是说,如果把初始时的 n 名乘客按照各自要去的层数从高到低(注意此顺序与输入相反)排列,并依此编号为第 1、2、3…n 个人,第 i 个人要去第 f[i] 层(f[1]>f[2]>…>f[n]),那么可以认为任意时刻电梯里乘客的编号都是 1, 2,..,x 这样一个连续序列。也就是说,对于电梯里的人我们只需要考虑编号为 1, 2, 3 或 1, 2, 3, 4, 5 这样连续排列的情况,而无需考虑 1, 2, 4(缺3)或2, 3, 4(缺1)这样的情况。

这样一来,每个状态都能由两个数[i,j]来表示:电梯在第i层,电梯里有j个人,即要去楼层最高的第1,2,..,j个人。
下面给出转移方程:
f[i,j]表示电梯在第i层,电梯上有要去楼层最高的j个人时,电梯上的人全部到达各自楼层所需的最短时间
1 2 3 4 5 | |
边界条件、最优解的构造方法以及其它细节问题不再赘述,详见代码。复杂度O(m*n^2)。代码中其实还有优化的空间,但已经是0ms过的,没必要了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | |
拿本本的摄像头录的,同步有点问题不想调了= = 能弹成这样我很满意了恩~
p.s. 使用了强大的开源视频处理软件 VirtualDub 进行视频捕获和剪辑,欢迎猛击链接到其主页围观。
]]>篇幅所限,书中还是有很多内容不够深入。一些内容需要另一本书的篇幅深入讨论(如重构),甚至需要看一本书才能从纸上谈兵转到实践(如TDD、Acceptance Tests)。。。望不到头的书单啊T_T
做笔记使用的工具是 XMind。
]]>有一串整数在排队……
有N个整数,你有一个可以框住M个连续段整数的木框,现在你想知道,对于这个队列中任意的连续M个整数,最大和最小的整数是哪个?
例:(M大小的窗口向右滑动)
1 2 3 4 5 | |
最后需要输出的是两行:
1 2 | |
第一行包含一个整数T,表示有T组测试数据; 以下每组测试数据格式: 第一行包含2个整数N,M表示有N个整数在排队,取连续M个整数。 第二行包含N个整数。 其中N不大于1,000,000,M不大于N。
按照题目描述格式输出结果,第一行为MIN,第二行为MAX。
软件学院07级数据结构第二次测试
很久不考虑算法问题了的说……当然也好久不写解题报告了。上面这题让我体会到久违了的思考的乐趣(i.e.我废掉好久了>_<)
最显然的解法是利用平衡树始终保持木框内M个数的有序状态,大体是O(NlogM)的复杂度。这个不用多说。
问题是:能否找到O(N)的解法?在读下去之前你可以好好想想。
我纠结了好久,终于……也没想出来- – 尽管想出很多优化,终究还是不能达到要求。网上搜了下,看到 CS大牛csdn 上有人给出了一个解法(你也可以在读完全文后再回头看这一段):
是可以到o(n);
编程之美上有一个类似的问题:”队列中取最大值操作问题”;
实际上窗口移动就相当于对队列做了一次出队与入队操作,所以lz这道题可以套用该解法;书上是使用两个栈来模拟队列,假设为分别A,B;
1)当入队的时候,push A;
2)当出队的时候,
a)若B非空,pop B,
b)若B为空,则先将A中的元素依次pop并push到B,再pop B;
这样,就使用两个栈达到了队列的功能;同时,对于单个栈,由于pop,push都是在栈顶进行的,所以每个栈都可以方便地维护自己的最
大值与最小值在栈内的索引;
以最大值为例;
用max_idx保存栈内最大值的索引;
另使用一个跟栈的最大长度一样的数组idx,idx[i]表示栈的索引范围在0到i-1的元素中的最
大值的索引为idx[i];
- 当push的时候,比较栈顶元素与栈的已保存的最大值,
- 若栈顶元素大于已保存的最大值,那么idx[top]=max_idx,max_idx=top;
- 若栈顶元素不大于已保存的最大值,那么idx[top]=max_idx(书上是idx[top]=-1);
- 当pop的时候,max_idx=idxtop;
具体到这个题目;
可以使用两个长度为m的栈来模拟窗口;
- 将前m个元素依次push到栈A;
- 移动窗口就相当于分别进行pop B 与 push A操作;
- 窗口的最大值==max[A.items[A.max_idx]),B.items[B.max_idx])];
由于每个元素最多进入两栈各一次;
所以总的复杂度是o(n)的;
这个解法的确可行,但描述仍不直观。为什么两个栈暧昧地眉来眼去一番,就在O(N)时间内把问题解决了呢?褪去实现上的种种细节,从更高的角度观察这个算法,它的思路是怎么样的呢?
和冬冬讨论了半天,似乎找到了一个比较靠近此算法根本动机的理解方向。如下:
此问题中,框的左端和右端同时在向右移动。这时,要在任意时刻用O(1)的时间获得框内最小值(最大值同理,故仅以最小值为例)并不容易。但是,如果假设这个框是可伸缩的,将其一端固定,仅移动另外一端,任意时刻能否在O(1)的时间内获得最小值呢?
稍加思考,就发现要找到这样的算法是很容易的(事实上在我最初考虑时,就是试图在这样一个算法的基础上进行优化的)。
假如这个框是左端固定,右端不断向右移动的(称之为左定右动框)。对于框内的每个数,有一个对应指针指向框内它左边(包括自身)所有数中最小者。这样,框内最小值就是框内最右端元素对应的指针所指向的元素。如在下图中,最小元素就是右端7对应指针所指向的元素6。

当框的右端向右扩展时,可以通过递推获得新元素对应指针应指向的元素。以上图为例。加入2时,由7对应的指针可知原框中最小值为6,而6>2,所以新元素2对应的指针应指向自己。

加入8时,由2的指针知原框中最小值为2,而2<8,所以新元素8对应指针应指向2的指针所指向的元素2。

其余如法炮制。

可见,在框左端固定、右端不断扩展时,任意时刻都能在常数时间内确定框内元素的最小值。(不仅如此,即使需要让右端向左收缩,也能在常数时间内,通过最右端元素指针获得得到收缩后框内所剩元素的最小值。) 整个过程演示如下(先扩张后收缩):

演示中的收缩操作在当前问题中并无必要。演示出来是为了说明无论右端如何移动,只要左端不动,就可以在任意时刻立刻得到最小值。同时也是为了与下面一个演示保持一致。
如果一个框右端固定,左端收缩(称之为右定左动框),也可以在任意时刻花费常数时间获得框内的最小元素。过程和上面左右对称。具体地说,每个元素对应一个指针,指向框内它右边(包括自身)所有数中最小者。首先需要从右到左计算出各个元素对应的指针。这个过程类似于将框的左端由右向左扩展。计算完毕后就可以在收缩左端的过程中在常数时间内获得最小元素了。
演示如下(先扩张后收缩,可以看到完全和上面对称):

对于当前问题,在“扩张”过程中并不需要求最小值,而仅仅需要计算出指针来为收缩过程作准备。这里在全过程中标注出最小值是为了说明任意时刻都有能力得到它,也为了与上一个演示保持一致。
上面拉拉杂杂说了一堆,只为了说明:如果框的一端固定,仅移动另外一端,则在此过程中可以仅花费常数时间获得框内的最小元素。针对左定右移、右定左移两种情况的算法是彼此对称的。
最后顺带提一点实现细节,希望不会影响你对整体的理解。上面所使用的指针也可以用索引号来代替,这里使用指针是为了显得更形象。事实上,不保存索引或指针而直接保存最小的“值”也是可行的,但是并不推荐。设想,如果每个元素不是数而是四五米长的字符串(>_<),偏序关系使用字符串长度的比较,那么如果不保存指针/索引而保存值,所带来的元素复制开销恐怕不是你想要的。
怎样把上面只允许一端移动的解法推广到同时允许两端移动呢?
一种想法是,将以上二者合二为一。具体地说,将框内的数看成左右两部分,左边一部分看成右定左动的,右边一部分看成左定右动的。这样,在左边收缩、右边扩张的过程中,左右两部分都可以在常数时间内得到最小元素。取两个最小元素中更小者,即为整个框中的最小元素。
OK,这就是O(N)算法的基本思路了。回到原题目,下面看一个例子。
假设我们要处理的是这么一串数:
1
| |
框的大小为5。
首先,把最先被框住的5个数看成被一个右定左动的框框住,这5个数右边看成是一个空的左定右动框。当然,首先需要计算出开始这5个元素对应的指针:

接下来,左边的右定左动框收缩,右边的左定右动框扩张。在此过程中,框在框中的M个元素的最小者可以在常数时间内获得。

到左边部分收缩至空时就没有办法继续收缩了。怎么继续这个过程呢?
解决方法是,将右端含有M个元素的左定右动框重新处理为右定左动框,并在右边再放上一个空左定右动框:

接下来继续这个过程就行了。下面是全过程的演示:

你可能会对每次将右边的左定右动框重新处理为左边的右定左动框(有点晕- -)时计算指针造成的额外开销存有疑虑。然而,每次重新计算时需要处理M个元素的指针,每隔M个元素才会进行一次这样的处理,N/M*M仍然为N,并不会升高复杂度的阶。换句话说,每个元素至多被重新计算两次指针,所以总体复杂度仍然为O(N)。
以上就是整个解法。
我们可以把右定左动框看成是一个底在右顶在左的栈,左端向左扩张看成是向栈push元素,左端向右收缩看成是栈在pop元素。左定右动框亦然。前面提到,两种框上的操作是对称的;如果把它们都看成栈,则push和pop时的操作完全一致。这样就将两种框对称的操纵统一在了同一个数据结构上,使得实现起来更为简洁。
这就是对开始所引用那段算法描述的解释。
更进一步,我们还可以使用类似的方法处理此问题的变种。如,对于一个队列,随意进行入队出队操作(相当于木框的宽度不再固定为M,其两端也不同时向右移动),求在任意时刻队列中的最小元素。或者,一个双端队列,在两端随意进行入队出队操作,求任意时刻队列中的最小元素。等等。
最后顺便一提,在搜索此题资料时发现这个问题似乎涉及到数据流的处理算法。这个方面我一无所知,没办法站在那样一个高度阐述,抱歉。
]]>前两天新开的数据结构课布置上机作业,里面又出现了不知道之前出现过多少次的输出质数。每次都交表也会很乏味,所以……这次还是要交表 - -||| 不过要玩一点小花样——让编译器在编译期把质数表算出来。
这个当然涉及到一点模板元编程了。之前虽然看了《Effective C++》里关于模板元的简介挺感兴趣,但看到《学习C++:实践者的方法》里告诫不要在这种“20%场景下的复杂性”上白花时间:
这些细节或技术在日常编程中极少用到,尤其是各种语言缺陷衍生出来的workarounds, 构成了一个巨大的长尾……绝大多数只在库开发当中需要用到
我因而一直对模板元编程敬而远之。这次也只是消遣一下,并无深入学习的打算。
以下是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | |
原理比较简单,主函数第一行初始化 PrimeInitializer 时,由于继承关系,构造函数会层层递归调用,从里至外利用另一个模板类 IsPrime 判断模板参数是否是质数,并把测试确认的质数放进一个 vector 里,这样就得到了编译期计算出的质数表。IsPrime 则使用最原始的试除方法判断质数。代码里一些写得很纠结的地方,一方面是为了尽量简化计算,减少编译时间,另一方面更主要是因为 g++ 默认最大只能实例化500层模板,以题目的数据规模(1000)不纠结一下编译器会抱怨。
这个程序……很不幸没能 AC。Buaa 的 OJ 在设计时估计就考虑了这种情况,对编译时间做出了限制,在编译20秒左右还没编译成功时会结束编译,直接判 CE……这个程序在我的系统上编译需要20分钟(VC 编译)到半个小时以上( g++ 编译)的时间,冬冬的64位 Ubuntu 上用 g++ 编译也需要将近10分钟时间。如果OJ不做这个限制估计会像当年vijos一样pending很多页吧……
恩,第一次模板元编程经历就以这样悲惨收场了 T_T
更新:
vijos果然被卡住了 – -

已作为bug报告。
]]>注意看歌词哈:
Deconstructing Johann
J. S. Bach had a little problem.
J. S. Bach was in a fix.
J. S. Bach couldn’t find an answer.
What to do?“I’ve written most of a rather fabulous work!
Toccata, it’s in D minor, but now I’m feeling a bit of a jerk!
I can’t think of what should come after it…”“Now,” said his wife, who was resting up after her 33rd child,
“Johann, my dear, you should just go to bed.
Something always comes up.”“Don’t be a tweet!
It’s a real crisis and I’m working to a deadline!
What can I fit?
What to fit after the great toccata?
Maybe it needs to be something faster?
I haven’t got a clue
and in a week the piece is due.
I’m in a panic!
I’m stuck like glue!”“Don’t get your knickers in a twist, Johann.
‘Those are only notes’, you’ve always said.
There’s only twelve so use your head.
How many arrangements of twelve notes can there possibly be?”“That’s a problem I don’t want to deal with.
How many permutations on C and D and E and F and G, A, B
is a thing that I never heard of.”
“You can leave that to Arnold Sch?nberg.
He is the person to do that twelve-tone thing.”“No!! No!!!
It isn’t the answer!
I haven’t the foggiest!
What am I gonna do?
I’m all in a panic!
Aaah! No!”“What can I do?
I’ve finished my toccata but I have no fugue.”
(Phone ringing)
“Aaah… and now I’ve got a fugue!”
笑料很多,慢慢欣赏~
]]>从去年NOIP06前夕到现在,一年多一点,就是我全部的OI之旅了。虽然非常羡慕那些强省的OIer可以很早就了解到OI之美,然而从NOIP06启航,到在WC里瞻仰群牛大开眼界,然后在省选时置于死地后又峰回路转,接着是NOI由于水平不足加之准备策略失误导致的惨败,最后是NOIP07并不完美的结局,我经历了一个OI巨菜所能期望经历的一切。有过泪水,有过欢笑,有过初涉OI时的无知无畏,也有过面对大牛时的相形见绌。这短暂却充实的一年因为经历丰富而显得格外漫长,而曾经的那一幕幕却仍如在昨日。我甚至还清楚地记得今年过年那天,我一个人呆在房子里,不顾隔壁的喧嚣,计算USACO里那道The Clocks的搜索树节点个数。那时候还是我刚刚入门、OI热情最高涨的时候啊,每次深夜关掉电脑从思考中回过神来都恍若隔世。
的确,每个OIer对于自己的OI之路都会有很深刻的记忆。我会一直记得USACO那些诡异的奶牛们和“Your Program Works The First Time”时的惊喜,记得Vijos刷出五六页Waiting时的万分焦急,记得20分钟啪啪啪拍出一套完整SBT后小小的得意,记得几个小时都过不了一道搜索时的郁闷,记得深夜里面对显示器又涩又痒的眼睛,记得大赛之前自己砰砰乱跳的心。短短一年的OI之路,曾经阳光明媚,也曾经曲折泥泞,不管怎么说,我跌跌撞撞地一直走了下来,我不曾后悔。
OI在每个OIer心中都已不仅仅是一项学科竞赛。即使不像Ghost说的达到“信仰”的高度,也是一种信念、一种精神。还有哪科竞赛会使我们收获全国范围内的真挚友谊?还有哪科竞赛会让我们在大年三十仍在群里一边开玩笑一边讨论算法?还有哪科竞赛会让我们在网上自发组织这么多自己出题(M67牛说这些题比NOIP07题的水平高多了)的模拟赛,甚至用举办模拟赛的方法庆祝七夕、庆祝光棍节、过生日甚至见证爱情?OIer是所有搞竞赛的学生中最另类的一些,OI也是唯一与高考科目无关的竞赛。我们因此也面对了更多的困难,经受着更大的考验,然而OI承载着OIer们的梦想,OIer们也为此付出了太多太多。
我曾经很多次感叹,我学到的越多就越清楚地意识到自己的无知。这确实是实话。OI和演讲比赛不同,我不需要刻意掩藏自己的胆怯,强做出一副自信满满、天不怕地不怕的样子。看到很多初中的小朋友就比自己强出很多,很羡慕他们的环境,而那种挫败感也是很难避免的。况且每天上网和那些不把北大清华当回事的大牛们待在一起,不生出些自卑情绪都难。但和他们在一起我也学会了很多东西,自己水平也有不少长进,生活也充实不少。我想起前一段时间自己在Vijos的签名:“我是巨菜,但我在尽我所能快速成长。”这话看着真提气。

每个OIer无论是牛是菜,在成长过程中都会得到很多无私的帮助,因而在退役的时候也会有一大堆要感谢的人。下面我也要列出一个名单,向帮助过我的人表达我的谢意。谢谢你们,如果没有你们,也许我根本不会走完这条并没有多长的OI之路。
首先要感谢我师傅大漠。师傅在OI方面也许没有我后来遇到的很多冲金夺银的大牛那么牛,但是在我OI之路开始于NOIP06迷茫无助之时,是师傅给我提供了最真诚热情的帮助,教给了我很多东西。正所谓 “师傅领进门”,对此我至今仍心存感激。
然后不能不提的就是我们甘肃大水牛Ghost~~他几乎是对我乃至这两届很多GSOIer的OI生涯影响最大的人了。无论是OI还是数学还是保送问题,我都缠着问他,还有很多重要的决定,都曾向他征求意见。Ghost人非常好,非常乐意帮助我们这些菜,也算是少数几个我不叫“大牛”的大牛了,即使叫也要叫“大水牛”-_-|||……很难想象一个其实有些不善言谈的人在OI方面能这么牛,而且在QQ上能如此口若悬河,尽妖孽之所极……GSOIer的诡异外号几乎都是他起的,还有“21727”、“interrosting”等等笑料。。。能通过OI认识这个鬼牛,也真是一大幸事~
很幸运遇到了我们的信息老师老赵,别的学校的OIer都很羡慕啊。咱老赵认真负责,每次比赛前都能收到她的短信或者Email,叮嘱种种细节,甚至包括吃巧克力后什么时候会有比较好的状态什么的,对我们的各种事情也都会很操心。为此清风都说后悔没来咱一中。
还有Ivan,也许真是有缘吧,WC回来以后莫名其妙地主动加了我=.=,然后才知道去fzyz的时候我们就坐的一辆面包车。Ivan是个非常有才华的OIer&MOer,也算是一个我不叫“大牛”的大牛吧。然而也总是不自信,非但从来不B4我这个巨菜,还真诚得让我有些受宠若惊……Ivan差不多是惟一一个说我很“cool” 的人(大汗--!),并且还感慨“真奇怪你怎么会没有GF” (瀑布汗--|||),于是我就说啊,Ivan,如果你是个PLMM就好了……总是会在我最迷茫的时候鼓励我,然后发一堆PLMM的PP过来(再瀑布汗--|||)。Ivan,就像你说的,也许我们不能做一辈子OIer,但我们能做一辈子朋友^^
哑熊、DFDNN、清风、Anti、BT、亚圣、Marchine,喊出这些名字感觉真亲切。战友也好,对手也罢,说到底我们都是GSOIer,都是非常非常好的朋友。真的很高兴能认识你们,能和你们一起努力。希望和我一样高三的OIer们都能保送到好大学去(Marchine大牛ms是要降50分去清华的)。至于高二的诸位,明年NOI就看你们的了。
感谢zmcplmmdn(=。=),一个未曾谋面的OIer,在每次比赛前都会bless我。NOI挂掉后也曾发来一条很长的信息鼓励我(这条信息我现在还存着呢),真的很感动。希望能在明年NOI的获奖名单上看到你,好好加油。
感谢DD牛和Matrix67牛,你们的教程让我受益匪浅。
感谢每个办模拟赛的大牛,通过这些模拟赛,我得以及时修正NOI时错误的答题策略,没有在NOIP时再次挂掉。
感谢身边所有的朋友。我没有像当年Ghost那样不被理解,甚至被B4;相反,你们给我的更多是鼓励和支持。等大学有着落了请你们早餐哈~ 至于那些曾在这一路上误导我甚至给我设障的人,我已经不记得你们了,也不想再提起你们,就这样。 虽然现在仍存有因我用longlong而被卡在全国线下的危险,但比结果更重要的是这一年我所经历的一切。这一年我失去了很多,也放弃了很多,但却在拼搏的汗水中成长,在失落的泪水中成熟,换来的是一个至少对自己来说不平凡的17岁。这一切,便是我给自己最好的成年礼物。今后我是否会成为一个ACMer?我不知道自己是否还有这样的勇气和实力,但我会一直努力。在OI并不完美的谢幕后,我迎来的是18岁全新的征程。
一直很遗憾OIer们没有一首自己的歌可以抒怀(《隐形的BUG》《爱在编译前》这些改编的搞笑之作应该不算)。在NOI07闭幕会上听到最后一首《拥抱明天》才发现OI精神于奥林匹克精神是何其相似:参与、坚持与友谊。而NOIP07前一天我在毫无准备的情况下听到这首歌时,一时间百感交集,竟禁不住泪水夺眶而出。本文不妨用这首歌的歌词做结吧:
]]>让我们的心相连
把我们的爱奉献
在飒爽英姿赛场上
相逢一笑到永远让我们的心相连
把我们的爱奉献
在奥林匹克旗帜下
拥抱明天
“子非我,安知我不知鱼之乐?”
“子非我,安知我不知子不知鱼之乐?”
“子非我,安知我不知子不知我知鱼之乐?”
“子非我,安知我不知子不知我知子不知鱼之乐?”
“子非我,安知我不知子不知我知子不知我知鱼之乐?”
“子非我,安知我不知子不知我知子不知我知子不知鱼之乐?”
“子非我,安知我不知子不知我知子不知我知子不知我知鱼之乐?”
“子非我,安知我不知子不知我知子不知我知子不知我知子不知鱼之乐?”
…………
现在你知道是谁发明递归的了。 @_@
]]>佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了“小教官”。在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会。一共有n个同学,编号从1到n。一开始,同学们按照1,2,……,n的顺序坐成一圈,而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序,形成新的一个圈,使之符合同学们的意愿,成为摆在佳佳面前的一大难题。
佳佳可向同学们下达命令,每一个命令的形式如下:
(b1, b2,… bm -1, bm)
这里m的值是由佳佳决定的,每次命令m的值都可以不同。这个命令的作用是移动编号是b1,b2,…… bm –1,bm的这m个同学的位置。要求b1换到b2的位置上,b2换到b3的位置上,……,要求bm换到b1的位置上。
执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置,那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿,你能帮助佳佳吗?
输入文件fire.in的第一行是一个整数n(3 <= n <= 50000),表示一共有n个同学。其后n行每行包括两个不同的正整数,以一个空格隔开,分别表示编号是1的同学最希望相邻的两个同学的编号,编号是2的同学最希望相邻的两个同学的编号,……,编号是n的同学最希望相邻的两个同学的编号。
输出文件fire.out包括一行,这一行只包含一个整数,为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望,则输出-1。
对于30%的数据,n <= 1000;
对于全部的数据,n <= 50000。
至此我就算做完了NOIP05提高组的全部四道题,发现除了第一题外其它题都不水,对于我这样的水平来说很值得一做。过河的状态压缩我想了好久;本题优化方法较过河简单,但也需要动动脑子;至于那个等价表达式则是典型的ws题:思想简单(特值法+用栈求表达式的值),但实现起来却比较繁琐,细节硕多,稍不留神就会出错,我前后一共提交了5次才AC(得到的分数分别是20、30、40、90、100)。还好这些题都是自己独立搞出来的。顺带一提,等价表达式取特值时并不像很多人说的要多个,一个足矣,当然不要取太特殊的,比如取a=1,-2,-3,0这样的就很容易被强数据阴掉,但要是取个a=-5.65742它不就没治了;同时B4数据中括号不匹配的情况。
当然这都是题外话,下面言归正传。
首先需要看到的是,虽然佳佳下达的命令形式很ws,但是在求总代价的时候却完全不需要管它。显然,在佳佳下达完一系列诡异的命令后,最后有几个人离开了原来的位置,这种情况下最小代价就是几(为什么?)。看清了这一点,问题就转化为:要使得所有人满意,最少需要让几个人离开原来的位置?
我们先考虑无解的情况。把每个人看成无向图中的节点,两人相邻则连一条边。当n个人以某种次序围坐成一个圈的时候,每个节点的度一定都是2。而这种状态一定是初始状态通过若干次次序调整能达到的,即一定有解。那么无解的情况就一定是,根据同学们的希望构图后有同学的度不为2。这样,我们只需要构图,然后看是否所有节点的度都为2就可以判断是否有解了。
如果有解,下面就需要计算最小代价了。对于我们构得的图,DFS(1)后一定得到唯一的一个序列。例如1,5,3,2,4。我们现在就需要比较它和初始状态1,2,3,4,5,看有几个人离开了原来的位置。但这个序列实际代表的是一个环,而且方向正反有两种(即1,3,5,2,4和4,2,5,3,1应该是等价的),我们就需要把初始序列正反分别“转”N次(即1,2,3,4,5; 5,1,2,3,4; 4,5,1,2,3; 3,4,5,1,2; 2,3,4,5,1 以及 5,4,3,2,1; 1,5,4,3,2; 2,1,5,4,3; 3,2,1,5,4; 4,3,2,1,5)和DFS得到的序列比较,看其中最少有几个位置上的人编号不相同,就得到了我们要求的最小代价。
DFS是O(N)的;旋转可以通过指针来实现,所以是O(1)的;每次比较是O(N)的,共进行2N次比较。因此总的复杂度是O(N^2)的。期望得分为30分(我没写过这方法哈)。
进行很多次旋转,每次都需要比较,这种方法实在太慢了,是整个算法的瓶颈。怎么改进呢?我们发现转来转去不管怎么转,任意两个人之间的相对位置关系在这过程中都不会变。于是想到做差。
1 2 3 4 | |
这表示序列1,5,3,2,4不转动时1,3两个人在原来的位置上,转动3个位置后5和2两个人在原来的位置上,转动4个位置后只有4一个人会在原来的位置上。这就是说,1,5,3,2,4与1,2,3,4,5在旋转后最多有2个位置上的人编号相同,即最少有3个位置上的人编号不相同。同理:
1 2 3 4 | |
1,5,3,2,4与5,4,3,2,1在旋转后最少有3个位置上的人编号不相同。
取其中的较小者(例子没举好,两个值相同了)为3,即最后要求的最小总代价为3。问题就算解决了。
算法流程总结如下:
1 2 3 4 5 6 7 8 | |
改进后的算法复杂度为O(N),实现起来很简单。期望得分为100。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
最后再给一个在vijos里看到的算法。我自己没有实现过,有兴趣的试试吧。
见过的最牛最简单的方法……
将此题转换为冒泡排序,记录下所有交换的次数和两数间的距离,加上就行了……—__—|||
]]>具体是这样的,我们反向思维,本来是要求一个有序数列求出成为无序数列的代价,现在我们把无序数列(即目标数列)进行冒泡排序,然后……就是这样…… 看完之后,偶巨汗……
在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点:0,1,……,L(其中L是桥的长度)。坐标为0的点表示桥的起点,坐标为L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数(包括S,T)。当青蛙跳到或跳过坐标为L的点时,就算青蛙已经跳出了独木桥。
题目给出独木桥的长度L,青蛙跳跃的距离范围S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。
输入文件river.in的第一行有一个正整数L(1 <= L <=10^9),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。
输出文件river.out只包括一个整数,表示青蛙过河最少需要踩到的石子数。
对于30%的数据,L <= 10000;
对于全部的数据,L <= 10^9。
一看题,这无后效性和最优子结构也太明显了,随手就可以列出转移方程。令dp[i]表示处于i位置时,最少还要踩到几个石子(包括当前点)才能到达对岸。bridge[i]为点i处的石子个数(0或1)。则
当0≤i<L时:dp[i] = min{dp[i+j]} + bridge[i] (S≤j≤T)
当i≥L时:dp[i]=0
问题的解即为dp[0]。
但是这样写完交上去最多只能得30分。直接硬搞,复杂度是O(L*(T-S))的,T-S最大为9,无关紧要,但是L的最大值则是令人发指的10^9(青蛙跳这么长距离不累死么 – -),不TLE……恐怕需要10年以后的CPU了。那怎么办呢?(思考过程也许比较繁琐,要结论请直接看倒数第5段)
数据范围往往是算法选择的最重要的提示。我们看到虽然L这么巨大,但是石子的总数却不超过100个。这说明什么?说明桥上必然有大段大段的无石子区间,而无石子区间的最大长度在极限情况下将接近10^9。即在跳跃中,青蛙经常会很郁闷地在巨长的无石子区间上跳啊跳啊,想踩个石子都踩不到。上面那个普通dp肯定会导致空白区间上大量的无必要决策而无谓耗费大量时间。要减少这种时间浪费,我们可以试图把大段的无石子区间等效转化为较短的无石子区间,从而使时间开销降至可承受范围。
首先考虑最简单的情况:S=T。这种情况下,这个诡异的青蛙只能跳固定的长度。而跳的起点是0位置,那么青蛙经过的就只有S(或者说T,一回事)的整数倍点。如果石子的位置为S的整数倍,那么青蛙就一定会踩到,否则一定踩不到。比如S=T=3,L=100,那么青蛙经过的点就为且仅为0,3,6,…,96,99。在21、27、33、66等处的石子就一定能踩到,而20、40、80、92等处的石子就踩不到。于是,当S=T时,我们只需要数位置为S整数倍的石子有几个就得到了答案,不需要进行上面说到的“等效转化”。
那如果S≠T呢?我们也从一个最简单的例子看起。假如S=5,T=6,青蛙从0位置开始跳,那么它可能到达的点是:
5,6,
10,11,12,
15,16,17,18,
20,21,22,23,24,
25,26,27,28,29,30,31,32,33,……
可以看到,这些点组成了一个个连续的区间,各相邻区间起点间的距离都是S=5,开始区间长度为2,然后长度变为3,4,然后完全连在一起,后面的位置都可达。如果桥上0~999都没有石子,1000处有一个石子,那么我们只关心青蛙是否选择跳到999,998,997,996,995,994这几个位置,因为青蛙在这段无石子区间上跳半天最后总会跳到这6个位置上,在无石子区间中不管怎么跳其实都无所谓。那么,我们完全可以无视25以后的数,把这段区间等效为0~25,以确保25以后所有位置都可达且有20~25这段连续区间来代替994~999这段等长的区间。这样,我们在这段无石子区间上的决策就大大减少了。
桥上的其他无石子区间是否可以如法炮制呢?答案是肯定的。对于以后任何一段长度大于25+6=31的无石子区间(这是采取“等效”措施的“门槛”),我们都可以把它的长度看成31。(25为什么要加上6呢?考虑一下,进入这段无石子区间后,青蛙开始往前跳的的起点,它可能不是区间本身的起点。)这样处理后,即使桥长度达到10^9,也可以在非常短的时间内出解。事实上,我们不仅可以把无石子区间长度等效为31,等效为36,121,500也都可以,只要长度比31大就行(当然“门槛”也要相应升高)。我们把这个等效区间的“最短”长度(本例中为31)称为“等效区间最短长度”。
下面推广。当S≠T时我们发现,在T不变的情况下,T-S越大(即S越小),等效区间最短长度越短。T-S不变的情况下,T越大,等效区间最短长度越长。那么对于题目给出的数据范围,S=9,T=10时(满足T-S最小且T最大)得到最大的“等效区间最短长度”为100。对于其他的S和T,我们不需要专门计算它们对应的等效区间最短长度,直接采用100这个值就可以了。
综上,若S=T,我们直接数位置能被S整除的石子个数;若S≠T,如果某无石子区间长度大于100,则等效为100,否则不变,然后再dp。
至于为什么有同学取比100小的数也AC了,我觉得(不一定对哈,没验证)应该是数据弱了。经实验,即使取20也可以AC,取10也才WA一个点。
对于等效区间最短长度的这番计算其实完全不必要。比赛时最好的方法是:取时间复杂度可接受的最大值……这样最省事。
上面可能做麻烦了,欢迎提供简明解法,谢谢。
最后说点题外话。这题我调了一晚上,郁闷死了。最后发现问题竟然是:石子位置没有按照升序排列,我却想当然这么处理了,结果一个点都过不去……
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |