Onion's profileOnion 的杂物箱PhotosBlogLists Tools Help
March 24

归去来兮……

搬家吧,搬家吧……
     从今天起这个Spaces就搬到FallingInTheSky.spaces.live.com了
     促成这次搬家的原因很多,比如Spaces杂乱的数据结构,无法收拾的烂摊子……我换了名字,因而换了邮箱,因而换了Passport,于是连共享空间也要换了……
     好吧,新家见。
March 21

转-后缀数组

在字符串处理当中,后缀树和后缀数组都是非常有力的工具,其中后缀树大家了解得比较多,关于后缀数组则很少见于国内的资料。其实后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。因此在本文中笔者想介绍一下后缀数组的基本概念、构造方法,以及配合后缀数组的最长公共前缀数组的构造方法,最后结合一些例子谈谈后缀数组的应用。


基本概念

首先明确一些必要的定义:

字符集 一个字符集∑是一个建立了全序关系的集合,也就是说,∑中的任意两个不同的元素α和β都可以比较大小,要么α<β,要么β<α(也就是α>β)。字符集∑中的元素称为字符。
字符串 一个字符串S是将n个字符顺次排列形成的数组,n称为S的长度,表示为len(S)。S的第i个字符表示为S[i]。
子串 字符串S的子串S[i..j],i≤j,表示S串中从i到j这一段,也就是顺次排列S[i],S[i+1],...,S[j]形成的字符串。
后缀 后缀是指从某个位置i开始到整个串末尾结束的一个特殊子串。字符串S的从i开头的后缀表示为Suffix(S,i),也就是Suffix(S,i)=S[i..len(S)]。

关于字符串的大小比较,是指通常所说的“字典顺序”比较,也就是对于两个字符串u、v,令i从1开始顺次比较u[i]和v[i],如果相等则令i加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v(也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍比较出结果,那么若len(u)<len(v)则认为u<v,若len(u)=len(v)则认为u=v,若len(u)>len(v)则u>v。
从字符串的大小比较的定义来看,S的两个开头位置不同的后缀u和v进行比较的结果不可能是相等,因为u=v的必要条件len(u)=len(v)在这里不可能满足。

下面我们约定一个字符集∑和一个字符串S,设len(S)=n,且S[n]='$',也就是说S以一个特殊字符'$'结尾,并且'$'小于∑中的任何一个字符。除了S[n]之外,S中的其他字符都属于∑。对于约定的字符串S,从位置i开头的后缀直接写成Suffix(i),省去参数S。

后缀数组 后缀数组SA是一个一维数组,它保存1..n的某个排列SA[1],SA[2],...SA[n],并且保证 Suffix(SA[i])<Suffix(SA[i+1]),1≤i<n。也就是将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA中。
名次数组 名次数组Rank=SA-1,也就是说若SA[i]=j,则Rank[j]=i,不难看出Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”。


构造方法
如何构造后缀数组呢?最直接最简单的方法当然是把S的后缀都看作一些普通的字符串,按照一般字符串排序的方法对它们从小到大进行排序。
不难看出,这种做法是很笨拙的,因为它没有利用到各个后缀之间的有机联系,所以它的效率不可能很高。即使采用字符串排序中比较高效的Multi-key Quick Sort,最坏情况的时间复杂度仍然是O(n2)的,不能满足我们的需要。
下面介绍倍增算法(Doubling Algorithm),它正是充分利用了各个后缀之间的联系,将构造后缀数组的最坏时间复杂度成功降至O(nlogn)。

对一个字符串u,我们定义u的k-前缀

定义k-前缀比较关系<k、=k和≤k:
设两个字符串u和v,
u<kv 当且仅当 uk<vk
u=kv 当且仅当 uk=vk
u≤kv 当且仅当 uk≤vk

直观地看这些加了一个下标k的比较符号的意义就是对两个字符串的前k个字符进行字典序比较,特别的一点就是在作大于和小于的比较时如果某个字符串的长度不到k也没有关系,只要能够在k个字符比较结束之前得到第一个字符串大于或者小于第二个字符串就可以了。
根据前缀比较符的性质我们可以得到以下的非常重要的性质:
性质1.1 对k≥n,Suffix(i)<kSuffix(j) 等价于 Suffix(i)<Suffix(j)。
性质1.2 Suffix(i)=2kSuffix(j)等价于
Suffix(i)=kSuffix(j) 且 Suffix(i+k)=kSuffix(j+k)。
性质1.3 Suffix(i)<2kSuffix(j) 等价于
Suffix(i)<kS(j) 或 (Suffix(i)=kSuffix(j) 且 Suffix(i+k)<kSuffix(j+k))。
这里有一个问题,当i+k>n或者j+k>n的时候Suffix(i+k)或Suffix(j+k)是无明确定义的表达式,但实际上不需要考虑这个问题,因为此时Suffix(i)或者Suffix(j)的长度不超过k,也就是说它们的k-前缀以'$'结尾,于是k-前缀比较的结果不可能相等,也就是说前k个字符已经能够比出大小,后面的表达式自然可以忽略,这也就看出我们规定S以'$'结尾的特殊用处了。

定义k-后缀数组SAk保存1..n的某个排列SAk[1],SAk[2],…SAk[n]使得Suffix(SAk[i]) ≤kSuffix(SAk[i+1]),1≤i<n。也就是说对所有的后缀在k-前缀比较关系下从小到大排序,并且把排序后的后缀的开头位置顺次放入数组SAk中。
定义k-名次数组Rankk,Rankk[i]代表Suffix(i)在k-前缀关系下从小到大的“名次”,也就是1加上满足Suffix(j)<kSuffix(i)的j的个数。通过SAk很容易在O(n)的时间内求出Rankk。
假设我们已经求出了SAk和Rankk,那么我们可以很方便地求出SA2k和Rank2k,因为根据性质1.2和1.3,2k-前缀比较关系可以由常数个k-前缀比较关系组合起来等价地表达,而Rankk数组实际上给出了在常数时间内进行<k和=k比较的方法,即:
Suffix(i)<kSuffix(j) 当且仅当 Rankk[i]<Rankk[j]
Suffix(i)=kSuffix(j) 当且仅当 Rankk[i]=Rankk[j]
因此,比较Suffix(i)和Suffix(j)在k-前缀比较关系下的大小可以在常数时间内完成,于是对所有的后缀在≤k关系下进行排序也就和一般的排序没有什么区别了,它实际上就相当于每个Suffix(i)有一个主关键字Rankk[i]和一个次关键字Rankk[i+k]。如果采用快速排序之类O(nlogn)的排序,那么从SAk和Rankk构造出SA2k的复杂度就是O(nlogn)。更聪明的方法是采用基数排序,复杂度为O(n)。
求出SA2k之后就可以在O(n)的时间内根据SA2k构造出Rank2k。因此,从SAk和Rankk推出SA2k和Rank2k可以在O(n)时间内完成。
下面只有一个问题需要解决:如何构造出SA1和Rank1。这个问题非常简单:因为<1,=1和≤1这些运算符实际上就是对字符串的第一个字符进行比较,所以只要把每个后缀按照它的第一个字符进行排序就可以求出SA1,不妨就采用快速排序,复杂度为O(nlogn)。
于是,可以在O(nlogn)的时间内求出SA1和Rank1。
求出了SA1和Rank1,我们可以在O(n)的时间内求出SA2和Rank2,同样,我们可以再用O(n)的时间求出SA4和Rank4,这样,我们依次求出:
SA2和Rank2,SA4和Rank4,SA8和Rank8,……直到SAm和Rankm,其中m=2k且m≥n。而根据性质1.1,SAm和SA是等价的。这样一共需要进行logn次O(n)的过程,因此
可以在O(nlogn)的时间内计算出后缀数组SA和名次数组Rank。

最长公共前缀
现在一个字符串S的后缀数组SA可以在O(nlogn)的时间内计算出来。利用SA我们已经可以做很多事情,比如在O(mlogn)的时间内进行模式匹配,其中m,n分别为模式串和待匹配串的长度。但是要想更充分地发挥后缀数组的威力,我们还需要计算一个辅助的工具——最长公共前缀(Longest Common Prefix)。
对两个字符串u,v定义函数lcp(u,v)=max{i|u=iv},也就是从头开始顺次比较u和v的对应字符,对应字符持续相等的最大位置,称为这两个字符串的最长公共前缀。
对正整数i,j定义LCP(i,j)=lcp(Suffix(SA[i]),Suffix(SA[j]),其中i,j均为1至n的整数。LCP(i,j)也就是后缀数组中第i个和第j个后缀的最长公共前缀的长度。
关于LCP有两个显而易见的性质:
性质2.1 LCP(i,j)=LCP(j,i)
性质2.2 LCP(i,i)=len(Suffix(SA[i]))=n-SA[i]+1
这两个性质的用处在于,我们计算LCP(i,j)时只需要考虑i<j的情况,因为i>j时可交换i,j,i=j时可以直接输出结果n-SA[i]+1。

直接根据定义,用顺次比较对应字符的方法来计算LCP(i,j)显然是很低效的,时间复杂度为O(n),所以我们必须进行适当的预处理以降低每次计算LCP的复杂度。
经过仔细分析,我们发现LCP函数有一个非常好的性质:
设i<j,则LCP(i,j)=min{LCP(k-1,k)|i+1≤k≤j} (LCP Theorem)

要证明LCP Theorem,首先证明LCP Lemma:
对任意1≤i<j<k≤n,LCP(i,k)=min{LCP(i,j),LCP(j,k)}
证明:设p=min{LCP(i,j),LCP(j,k)},则有LCP(i,j)≥p,LCP(j,k)≥p。
设Suffix(SA[i])=u,Suffix(SA[j])=v,Suffix(SA[k])=w。
由u=LCP(i,j)v得u=pv;同理v=pw。
于是Suffix(SA[i])=pSuffix(SA[k]),即LCP(i,k)≥p。 (1)

又设LCP(i,k)=q>p,则
u[1]=w[1],u[2]=w[2],...u[q]=w[q]。
而min{LCP(i,j),LCP(j,k)}=p说明u[p+1]≠v[p+1]或v[p+1]≠w[q+1],
设u[p+1]=x,v[p+1]=y,w[p+1]=z,显然有x≤y≤z,又由p<q得p+1≤q,应该有x=z,也就是x=y=z,这与u[p+1]≠v[p+1]或v[p+1]≠w[q+1]矛盾。
于是,q>p不成立,即LCP(i,k)≤p。 (2)
综合(1),(2)知 LCP(i,k)=p=min{LCP(i,j),LCP(j,k)},LCP Lemma得证。

于是LCP Theorem可以证明如下:
当j-i=1和j-i=2时,显然成立。
设j-i=m时LCP Theorem成立,当j-i=m+1时,
由LCP Lemma知LCP(i,j)=min{LCP(i,i+1),LCP(i+1,j)},
因j-(i+1)≤m,LCP(i+1,j)=min{LCP(k-1,k)|i+2≤k≤j},故当j-i=m+1时,仍有
LCP(i,j)=min{LCP(i,i+1),min{LCP(k-1,k)|i+2≤k≤j}}=min{LCP(k-1,k}|i+1≤k≤j)
根据数学归纳法,LCP Theorem成立。

根据LCP Theorem得出必然的一个推论:
LCP Corollary 对i≤j<k,LCP(j,k)≥LCP(i,k)。

定义一维数组height,令height[i]=LCP(i-1,i),1<i≤n,并设height[1]=0。
由LCP Theorem,LCP(i,j)=min{height[k]|i+1≤k≤j},也就是说,计算LCP(i,j)等同于询问一维数组height中下标在i+1到j范围内的所有元素的最小值。如果height数组是固定的,这就是非常经典的RMQ(Range Minimum Query)问题。
RMQ问题可以用线段树或静态排序树在O(nlogn)时间内进行预处理,之后每次询问花费时间O(logn),更好的方法是RMQ标准算法,可以在O(n)时间内进行预处理,每次询问可以在常数时间内完成。
对于一个固定的字符串S,其height数组显然是固定的,只要我们能高效地求出height数组,那么运用RMQ方法进行预处理之后,每次计算LCP(i,j)的时间复杂度就是常数级了。于是只有一个问题——如何尽量高效地算出height数组。
根据计算后缀数组的经验,我们不应该把n个后缀看作互不相关的普通字符串,而应该尽量利用它们之间的联系,下面证明一个非常有用的性质:
为了描述方便,设h[i]=height[Rank[i]],即height[i]=h[SA[i]]。h数组满足一个性质:
性质3 对于i>1且Rank[i]>1,一定有h[i]≥h[i-1]-1。
为了证明性质3,我们有必要明确两个事实:

设i<n,j<n,Suffix(i)和Suffix(j)满足lcp(Suffix(i),Suffix(j)>1,则成立以下两点:
Fact 1 Suffix(i)<Suffix(j) 等价于 Suffix(i+1)<Suffix(j+1)。
Fact 2 一定有lcp(Suffix(i+1),Suffix(j+1))=lcp(Suffix(i),Suffix(j))-1。
看起来很神奇,但其实很自然:lcp(Suffix(i),Suffix(j))>1说明Suffix(i)和Suffix(j)的第一个字符是相同的,设它为α,则Suffix(i)相当于α后连接Suffix(i+1),Suffix(j)相当于α后连接Suffix(j+1)。比较Suffix(i)和Suffix(j)时,第一个字符α是一定相等的,于是后面就等价于比较Suffix(i)和Suffix(j),因此Fact 1成立。Fact 2可类似证明。

于是可以证明性质3:
当h[i-1]≤1时,结论显然成立,因h[i]≥0≥h[i-1]-1。
当h[i-1]>1时,也即height[Rank[i-1]]>1,可见Rank[i-1]>1,因height[1]=0。
令j=i-1,k=SA[Rank[j]-1]。显然有Suffix(k)<Suffix(j)。
根据h[i-1]=lcp(Suffix(k),Suffix(j))>1和Suffix(k)<Suffix(j):
由Fact 2知lcp(Suffix(k+1),Suffix(i))=h[i-1]-1。
由Fact 1知Rank[k+1]<Rank[i],也就是Rank[k+1]≤Rank[i]-1。
于是根据LCP Corollary,有
LCP(Rank[i]-1,Rank[i])≥LCP(Rank[k+1],Rank[i])
=lcp(Suffix(k+1),Suffix(i))
=h[i-1]-1
由于h[i]=height[Rank[i]]=LCP(Rank[i]-1,Rank[i]),最终得到 h[i]≥h[i-1]-1。

根据性质3,可以令i从1循环到n按照如下方法依次算出h[i]:
若Rank[i]=1,则h[i]=0。字符比较次数为0。
若i=1或者h[i-1]≤1,则直接将Suffix(i)和Suffix(Rank[i]-1)从第一个字符开始依次比较直到有字符不相同,由此计算出h[i]。字符比较次数为h[i]+1,不超过h[i]-h[i-1]+2。
否则,说明i>1,Rank[i]>1,h[i-1]>1,根据性质3,Suffix(i)和Suffix(Rank[i]-1)至少有前h[i-1]-1个字符是相同的,于是字符比较可以从h[i-1]开始,直到某个字符不相同,由此计算出h[i]。字符比较次数为h[i]-h[i-1]+2。

设SA[1]=p,那么不难看出总的字符比较次数不超过

也就是说,整个算法的复杂度为O(n)。
求出了h数组,根据关系式height[i]=h[SA[i]]可以在O(n)时间内求出height数组,于是
可以在O(n)时间内求出height数组。

结合RMQ方法,在O(n)时间和空间进行预处理之后就能做到在常数时间内计算出对任意(i,j)计算出LCP(i,j)。
因为lcp(Suffix(i),Suffix(j))=LCP(Rank[i],Rank[j]),所以我们也就可以在常数时间内求出S的任何两个后缀之间的最长公共前缀。这正是后缀数组能强有力地处理很多字符串问题的重要原因之一。
March 11

XDeskShow

    其实就是个桌面工具软件,之所以拿出来介绍,是因为它最近把Sidebar从我的Vista上挤掉了。
    只要一说Sidebar,这个东西的功能大家都知道了吧,它的优点是可以自由调节各插件的大小、透明度、位置、总在最前、鼠标穿透……另外它还可以从官方网站上下载大量插件(有Sidebar全套噢)。另外就是内存占用很小(看起来只有几百k,也许是插入了Explorer进程)。
    官方网站:http://www.cfishsoft.com/xds/
    附个截图:

Rikirin画集

    图早就发上来了,补个说明……
    最初知道这个人是因为我现在的桌面壁纸左下角那个几乎辨认不出的签名,然后出于对这种风格画的喜爱就去google搜他(她?)的其它画,结果……令人大失所望啊……原来我的壁纸其实是他最特殊的一幅作品,不过为了不白花一番功夫,发上来。
    其中风格最不一样的那幅就是我的壁纸。
March 04

转-07年重庆队选拔赛试题 Matrix67首发

NOI2007全国青少年信息学奥林匹克竞赛


重庆队选拔赛


题目名称    余数之和    三角形       矩形        涂色
英文代号    sum         tri          rect        paint
输入文件名  sum.in      tri.in       rect.in     paint.in
输出文件名  sum.out     tri.out      rect.out    paint.out
时限        1秒         1秒          5秒         5秒
测试点个数  10          10           10          10
总分        100         100          100         100


时间:2007年3月3日



余数之和

sum


    给出正整数n和k,计算j(n,k)=k mod 1+k mod 2+k mod 3+ ... +k mod n的值,其中k mod i 表示k除以 i 的余数。
    例如j(5,3)=3 mod 1+3 mod 2+3 mod 3+3 mod 4+3 mod 5=0+1+0+3+3=7

[输入]
    输入仅一行,包含两个整数n,k。

[输出]
    输出仅一行,即j(n,k)。

[样例输入]
5 3

[样例输出]
7

[限制]
50%的数据满足:1<=n,k<=1000
100%的数据满足:1<=n,k<=10^9

三角形

tri


    画一个等边三角形,把三边的中点连接起来,得到四个三角形,把它们称为T1,T2,T3,T4,如图1。把前三个三角形也这样划分,得到12个更小的三角形:T11,T12,T13,T14,T21,T22,T23,T24,T31,T32,T33,T34,如图2。把编号以1,2,3结尾的三角形又继续划分……最后得到的分形称为Sierpinski三角形。

    如果B不包含A,且A的某一条完整的边是B的某条边的一部分,则我们说A靠在B的边上。例如T13(注:官方订正为23)靠在T24和T4上,但不靠在T32上。给出Spierpinski(注:原文如此)三角形中的一个三角形,找出它靠着的所有三角形。

[输入]   
    输入仅一行,即三角形的编号,以T开头,后面有n个1到4的数字。仅最后一个数字可能为4。

[输出]
    输出每行一个三角形编号,按字典序从小到大排列。

[样例输入]
T312

[样例输出]
T314
T34
T4

[限制]
50%的数据满足:1<=n<=5
100%的数据满足:1<=n<=50

矩形

rect


    给一个a*b矩形,由a*b个单位正方形组成。你需要沿着网格线把它分成分空(注:错别字,原文如此)的两部分,每部分所有格子连通,且至少有一个格子在原矩形的边界上。“连通”是指任两个格子都可以通过水平或者竖直路径连在一起。
    求方案总数。例如3*2的矩形有15种方案。

[输入]
    输入仅一行,为两个整数a,b。

[输出]
    输出仅一行,即方案总数

[样例输入1]
3 2

[样例输出1]
15

[样例输入2]
3 3

[样例输出2]
52

[限制]
50%的数据满足:1<=a<=4, 2<=b<=5
100%的数据满足:1<=a<=6, 2<=b<=7



涂色

paint


    假设你有一条长度为5的木版,初始时没有涂过任何颜色。你希望把它的5个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为5的字符串表示这个目标:RGBGR。
    每次你可以把一段连续的木版涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木版涂成RRRRR,第二次涂成RGGGR,第三次涂成RGBGR,达到目标。
    用尽量少的涂色次数达到目标。

[输入]
    输入仅一行,包含一个长度为n的字符串,即涂色目标。字符串中的每个字符都是一个大写字母,不同的字母代表不同颜色,相同的字母代表相同颜色。

[输出]
    仅一行,包含一个数,即最少的涂色次数。

[样例输入1]
AAAAA

[样例输出1]
1

[样例输入1] (注:原文如此)
RGBGR

[样例输出1]
3

[限制]
40%的数据满足:1<=n<=10
100%的数据满足:1<=n<=50

07年重庆队选拔赛试题--部分题解

    sum
    这道题相当令人无语。正解是O(sqrt(k))的。
    首先,从k mod 1到k mod Sqrt(k)没有规律,只能硬算。接下来就很科学了,为了方便,倒着说。
    n>k时n mod k肯定是k,这个没得说。强的是在n=k div 2到k的时候n mod k的值是一个等差数列,末项(即k mod k)为0,公差为1;而在n=k div 3到k div 2-1时n mod k的值也是一个等差数列,公差为2;当n=k div 4到…………
    这样用Sqrt(n)的时间算出前Sqrt(n)项,再用同样的时间用等差数列求和公式求出后面的Sqrt(n)个等差数列和,问题就被完美解决了。
    至于考场上怎么能想到这个……写个程序找规律是正解……
    tri
    不说了……自己模拟
    rect
    找个时间补上……
    paint
    很容易想到贪心,但看见n<=50,估计没几个人敢写……
    DP,状态F[i,j,k](1<=i,j<=n,1<=k<=26)表示将i到j的木板在使用k颜色为最底层颜色的情况下所需最少颜色数,则
    F[i,j,k]:=Min{Min{F[i,q,p](1<=p<=26),F[i,q,k]-1}+Min{F[q+1,j,p](1<=p<=26),F[q+1,j,k-1}+1,(i<=q<=j-1)}
    状态数为O(n^2*26),决策代价为O(n*26),合起来总的时间复杂度为O(n^3*26^2)。
March 03

几个关于数据类型的问题

    Pascal的数据类型实在令人恼火。上一次Noip栽在了string上,这一次又倒在int64脚下……把这两个问题贴出来,防止比我还菜的鸟儿们再犯……
    1:FP的string只有255位!虽然表面上string可以任意读入、操作,但它的确只有255位,如果你想存更长的字符串,请用Ansistring。
    2:FP新增了一个类型int64,这是个好冬冬……好多高精可以不用写了。但int64有一个致命的bug,由于FP本身仍是32位编译器,因此诸如m(int64):=2000000000*2000000000之类的语句仍然会得到错误的结果(2000000000在longint范围内,但它的平方超过了longint)。m(int64):=a(longint)*b(longint)如果结果超了longint的话也一样。
    解决方法是先把两个乘数赋给两个int64变量(尽管它们可能没有超过longint),再做乘法……

DP素王道?

    最近比赛真是疯了……上次Noip就是两道DP,这次重庆队选拔赛(http://www.matrix67.com/blog/article.asp?id=186)又是两道,难道DP要称霸OI界了?
    数学老师出题……太可恶了……
February 26

Sliding Window问题的O(n)算法

    这个问题似乎没有中文翻译啊……姑且按Matrix牛的叫法,叫滑动窗口问题好了。
    问题的内容是,给一串长度为n的数字串,在其中找连续子串,使得和等于k,问有多少个这样的子串。
    朴素算法谁都想得出来:枚举子串的头和尾,再计算和是否等于k,这样做是O(n^3)的(平方时间枚举加上线性时间计算)。稍微强一点的,可以预处理,预先用O(n)的时间求出前k个数的和F[k],计算串i-j的和时只需F[j]-F[i-1]即可,这样时间降到了O(n^2)。
    接下来科学的算法登场……
    设两个指针i,j初始时两者都指向1,随后每一次对串i-j求和,若小于k则将j向右移一位;大于则将i向右移一位;等于当然就计数,然后将i,j都向右移一位。直到j移出串末端为止。注意因为每次只移动一位,因而可以用一个变量m来记录串i-j的和,j向右移就把m加上新纳入串的数,i向右移则减去移出串的数,这样时间就是线性的了。
February 20

DP笔记(1)

1:巧妙的防重。
    考虑这样一个问题:给出N种钱币,每种面值不同,问可以组合出多少不同面值和的组合。
    很明显的DP,但如果要求每种钱只能用一次呢?这便要求我们进行防重叠处理。
    这也很简单,只要将面值数作为外循环,钱币种类作为内循环就可以了(仔细想想吧!)
   
2:合唱队形锯齿版
    合唱问题的加强版本,对队形的要求变为了,一高一矮一高一矮…………
    将状态增加一维,设F[i,j](j∈{0,1}),F[i,1]表示当第i个人作为“山峰”时i之前队列的最大长度,F[i,0]则表示第i个人作为“山谷”时i之前队列的最大长度,则有
      F[i,0]=Max{ F[k,1]+1 (h[k]>h[i],k∈[1,i-1]) }
      F[i,1]=Max{ F[k,0]+1 (h[k]<h[i],k∈[1,i-1]) }
    问题顺利解决
   
3:最大叉个数
    考虑这样一个问题:给定两个数列,如果数列1中某个数与数列2中某个数相等则可以在它们之间画一条线,找出一种划线方法,使得数列间的叉最多且叉之间不交错,如:
        1 2 3 4 5 6 7
        2 1 4 5 3 6 5
     其中可以画3把叉,分别是 1-1,2-2;3-3 4-4;5-5 6-6(下面的5指第二个5)
        1  2  3  4  5  6  7
         \/     \/    \|
         /\     / \    | \
        2  1  4  5  3  6  5
     而类似
        3  4  5
          \/  /
          / \/
         /  / \
        4  5  3
     的情况是不允许的。
    
    状态很好分:F[i,j]表示第一个数列的第i个元素,第二个数列第j个元素前,可以画出的
    首先明确,当出现一对多的情况时,如果要在i或j上画一条线,则该线越靠后越好,如
   
    ……4 5 6
    ……6 6 7
    则线画在后面的6上要好一些,因为如果在前面可以划一把连接前一个6的叉,则当前连上前一个6后,后面的6就被浪费了
    另外,只要i,j在前面都有相等的数,则必然可以画一把叉(不考虑交错)。
    这样我们的决策就很简单了:
    首先,如果要在i,j上都画线形成一把叉,则从i,j往前分别找最近的相等的数(如果数的大小有限制且都为整数,则这个过程可以预处理),位置设为a,b,则F[i,j]=F[a-1,b-1]+1
    如果只在i上画线,则F[i,j]=F[i,j-1]
    如果只在j上画线,则F[i,j]=F[i-1,j]
    如果既不在i,也不在j上画线,则F[i,j]=F[i-1,j-1]
    以上几种情况取一个最大值即可
   
4:???
    这个问题似乎没有正式名字啊……
    给出一个整数数列,将每个数增加或减少一定值(当然也可以不变),使得改变后的数列呈阶梯状上升(即每个数都大于或等于前一个数)。要求改变量的绝对值之和最小。
    这个问题的状态和前一个同样好分:F[i,j]表示将第i个数变为j时它和前边已经改变的量的绝对值之和。
    朴素的方法大家都应该想得到:设原数列为h,枚举k<=j,取F[i-1,k]+|h[i]-j|的最小值,这样一来时间复杂度是O(nm^2)(m指数列中最大数的大小)的,有没有优化方法呢?
    答案显然是有的,仔细观察我们可以发现,在对k的枚举中,我们做了重复计算,因为当前F[i,j]所枚举的k,在算F[i,j-1]时也一定枚举过(k=j时除外),于是我们的状态转移方程就变成了:
    F[i,j]=Min{F[i-1,j]+|h[i]-j|,F[i,j-1]±1(取决于当前j-1是否大于h[i])}
    这样就将时间优化到了O(nm)
5:最优凸多边形三角剖分
    问题很简单,就是将一个n凸边形从顶点剖分为n-2个三角形,使总边长和最小。
    还是先说朴素算法,对于某一个凸变形,它的剖分后最小边长和(当然三角形除外,它的边长和只有一个),就是枚举它的任两个不重合的顶点,将它剖分为另外两个凸边形后的这两个凸边形的剖分后最小边长和的和的最小值(很晕哈!),这样递归地做下去,分出三角形后直接求边长和。就能得到整个凸多边形的剖分后最小边长和。
    这样算的话时间复杂度为O(n^4),相当恐怖。画出状态树后我们很容易发现,完全没必要枚举那么多顶点对,事实上,对于任意一个顶点,在最终的任意一个剖分中必然有它被作为一个剖分开的点或者它左右两个顶点被作为剖分点,这样一来,决策就得到了简化:任选一个顶点,枚举其它n-3个顶点和它构成顶点对进行剖分,或者选择它左右两个顶点作为顶点对进行剖分,取最小值。这种决策方法可以覆盖所有情况,且时间复杂度降到了O(n),所以总的时间复杂度降到了O(n^3)。
   
6:LCS,LIS,LCIS……
    最长公共子串,最长上升子串,最长公共上升子串……
    两个字符串的公共子串很好求,设F[i,j]表示第一个串i个字符,第二个串j个字符及其之前的最长公共子串的长度,原串分别为s1,s2,则有
            ┍ F[i-1,j-1]+1 (s1[i]=s2[j])
    F[i,j]=│  
            ┕ Max{F[i-1,j],F[i,j-1]} (s1[i]<>s2[j])
    通过记录每步决策,就能求出这个串本身。
   
    最长上升子串也很好求。一种方法是将原串排序后对原串求LCS;另一种方法是:设F[i]为第i个字符及其之前最长上升子串的长度,原串为s,则
    F[i]=Max{F[k]+1(k∈[1,i-1] 且 s[k]<s[i])}
    同样可以通过记录每步决策求出这个串本身。
   
    至于LCAS,我知道的方法就是先对两个串求LCS,再将其中一个原串排序后对求出的LCS再求一遍LCS,虽然繁琐,但肯定是正确的,并且好像找不到更好的方法了。
   
7:状态压缩的DP
玉米地
问题描述
    农夫约翰购买了一处肥沃的矩形牧场,分成M*N(1<=M<=12; 1<=N<=12)个格子。他想在那里的一些格子中种植美味的玉米。遗憾的是,有些格子区域的土地是贫瘠的,不能耕种。
    精明的约翰知道奶牛们进食时不喜欢和别的牛相邻,所以一旦在一个格子中种植玉米,那么他就不会在相邻的格子中种植,即没有两个被选中的格子拥有公共边。他还没有最终确定哪些格子要选择种植玉米。
未完待续……
    作为一个思想开明的人,农夫约翰希望考虑所有可行的选择格子种植方案。由于太开明,他还考虑一个格子都不选择的种植方案!请帮助农夫约翰确定种植方案总数。
    直接计算的话,状态有2^(12^2)种,明显太大。
    MN,都小于12,且每个格子只有两种状态(不能耕种的格子可以看作始终已经种了的),这就为状态压缩提供了可能。
    考察每一行(当然列也可以),最多有12个格子,且每个格子有两种状态,则每一行最多可能存在2^12=4096种状态。由没有两个被选中的格子拥有公共边可以得出哪些状态可以相邻。
    设F[i,j]表示第i行,状态为j(状态为二进制,0表示种了,1表示没中,将其转为十进制)时的种植方案数,则F[i,j]就等于F[i-1]中所有能与j相邻的状态k的F[i-1,k]之和,当然如果j把玉米种在了贫瘠的地上,则F[i,j]直接为0。
    扫描最后一行所有可行的状态,将它们的F相加即可出解。
    这样一来,时间就被控制在了O(2^n*m),对于M*N(1<=M<=12; 1<=N<=12),加上位运算优化的话,完全足够了。
   
    Matrix牛上次写了一个加了位运算的程序,真是只能用“飞快”来形容呵!
   
February 08

一种新思路

例:
工序安排
问题描述
    一家工厂的流水线正在生产一种产品,这需要两种操作:操作A和操作B。有M1(1<=M1<=30)个机器可以完成操作A(且只能进行操作A,不能进行操作B),有M2(1<=M2<=30)个机器可以完成操作B(同样不能完成A操作)。初始时,输入容器有N(1<=N<=1000)个工件需要被加工。A型机器从输入容器接受工件,对其施加操作A,得到的中间产品存放在缓冲容器。B型机器从缓冲容器接受中间产品(这些产品均已被某一台A型机器加工过),对其施加操作B,得到的最终产品存放在输出容器。每台机器最多只能同时加工一个工件,所有的机器平行并且独立地工作。每台机器的工作效率可能不同,即不同的机器完成一次操作的时间不同。
    我们想知道,要想尽快结束所有的A操作,最少需要花费多少时间。当然,我们也想知道,完成所有的A、B两个操作最少需要多少时间。
输入格式
    第一行有三个用空格分开的整数N、M1、M2。
    第二行输入M1个正整数,表示M1台A型机器完成一次操作各自需要的时间。这些正整数保证不超过20。
    第三行输入M2个正整数,表示M2台B型机器完成一次操作各自需要的时间。这些正整数保证不超过20。
输出格式
    第一行输出一个整数,表示完成所有A操作的时间总和的最小值。
    第二行输出一个整数,表示完成所有B操作的时间总和的最小值(当然所有工件在进行B操作前需要先完成A操作)。
样例输入
5 2 3
1 1
3 1 4
样例输出
3
5
    初看这道题,马上想到了贪心,的确,贪心是有正确策略的。但无疑太麻烦。Matrix牛昨天讲了一个更“科学”的方法。
    首先我们如果知道一个所有A操作的时间总和的值,那么每个机器工作的情况也就一目了然(在有限的时间内尽量往里面塞即可),同时我们也知道这个时间总合是否合法(即这段时间内能否加工完足够多的零件)。并且从题目中又知道时间最多20*1000=20000且都为整数,于是我们可以从小到大枚举时间,直到枚举到一个合法的时间为止。这样就很简单地将第一个问解决了。
    这为我们解题提供了一个新的思路:枚举答案,在某些答案范围为整数,且答案容易验证正确性却不容易求出的场合十分有效。
    事实上,我们可以对答案(这个例子中是时间)进行二分,这样速度就更快了。
February 06

矩阵乘法

    今天讲矩阵乘法了……
    Matrix大牛来讲Matrix乘法,呵呵
    矩阵乘法,顾名思义,就是两个矩阵的乘法,定义新矩阵为x*y的矩阵C,两个相乘的矩阵为x*z的B,z*y的C,则有
                       z
             C[i,j]= ∑  B[i,k]C[k,j]
                     k=1
                   
    从定义可看出,如果两个矩阵可以相乘,则第一个矩阵的列数必定等于第二个矩阵的行数,因而一个矩阵如果可以乘以自己,则它的行数一定等于列数。
    举个例子
 
          / 1  2 \     / 2  3  4 \     / 1*2+2*5  1*3+2*6  1*4+2*7 \
          |       |    |            |    |                                          |
          |       | * |            | = |                                          |
          \ 3  4 /     \ 5  6  7 /     \ 3*2+4*5  3*3+4*6  3*4+4*7 /
    矩阵乘法满足结合律但不满足交换律。由于它满足结合律,因而可以使用二分法,这就使得n个矩阵的乘法可以在log2(n)*xyz的时间内求解这就给某些运算提供了速度上极大的飞跃。
   
    例1:在很短时间内求菲波拉契数列第一亿项。
    通常的方式是用递推公式F(n)=F(n-1)+F(n-2)直接计算,但这样的方法明显太慢。让我们试试矩阵乘法:
    例如由F(4)=3 F(5)=5推F(6)
          / 0 1 \    / 3 \      / 0*3+1*5 \    / 5 \
          |      | * |    |  = |              |= |     |
          \ 1 1 /    \ 5 /      \ 1*3+1*5 /    \ 8 /                顺利得到结果
 
    很明显,矩阵第一行的{0 1}可将5挪动到3的位置上,而第二行{1 1}则是用于计算3+5的。
    很明显,将矩阵 / 1 \           乘以             / 0 1 \   一亿次即可得到结果
                       |    |                             |      |
                       \ 1  /(数列的第1,2项)     \ 1 1 /
    由于矩阵乘法可以二分,因而算一亿次所用的计算次数为
             log2(100000000)*2*2*1=106.3
    远少于一亿次。
    事实上任何线性递推方程都可以使用这个方法求

关于条件概率

    昨天Matrix大牛讲了一个笑话:据统计,某一个航班上有炸弹(!!)的概率是1/100000,这个数字不算大,但如果考虑到每天世界上的航班数,这个数字就不小了,于是,我就自己带了一个炸弹上飞机,由于某一个航班上有两个炸弹(!!!!)的机率只有1/10000000000,而我又不是恐怖分子,不会引爆炸弹。所以我就不需要太过当心可能出现的第二枚炸弹了。
    但这显然有问题!这样一来,我如果怕炸弹,只要自己带一个炸弹就成了(!!!!!!)
    于是就引出了条件概率的问题。
    条件概率指的是在某一个事件确定发生时另一个事件发生的概率。设确定发生的事件为B,则另一事件发生的条件概率F(A|B)定义为:
             F(A|B)=F(A∩B)/F(B)
             其中F(A∩B)指A B 同时发生的概率
    这样一来,上面的问题就解决了。(A为一个炸弹在飞机上的概率,B为另一个在飞机上的概率)   
   
   
    顺便说一个问题:在外国有一种抽奖游戏,游戏中有三个门,其中两个后面是山羊,另外一个是汽车,游戏者先选一个门(当然游戏者是不知道门后是羊还是车的了),然后由知道答案的主持人打开一个不是游戏者选的且有羊的门(由于有两个门后面是羊,因此总可以打开这样一个门),问游戏者换不换刚才的选择,问换和不换门选到车的概率各为多少。
    表面上来看,此刻的两个门中一个是车,一个是羊,换和不换似乎都是1/2,但实际上不是的,实验证明(实践是检验真理的唯一标准!!)换门的换到车机率是2/3。几百年来,无数怎么想也想不通,总认为是1/2的人前赴后继地挑战着实践,人们也想了各种方法来说服他们。
   
    这个问题可以用条件概率讨论。
    设换门后选到车为A,第二次选择(由于只有两个门,选换和不换也就是选择某个门)选对了为B。A∩B即换门后选到了车且第二次选对了,换句话说就是第一次选错了,这个概率为2/3(即换门后可以选到车)*1/2(第二次选对)=1/3,而F(B)=1/2,代入式子得
    F(A|B)=(1/3)/(1/2)=2/3
    结果吻合。
   
    当然,更简单的方法是,如果选择换门得到了车,那么换门前必定选的是羊,那选到羊的概率就不用说了:2/3。同样可以解决问题。

简单概率分析

    昨天学概率啦,好恐怖!
    讲讲一个著名的概率问题
    有三个神枪手决斗,甲的命中率100%(哇……),乙为80%,丙为50%,现在三人先抽签决定开枪顺序。问三个人生存的概率为多少……
    关于策略的选取,想必大家都知道,最弱的生存几率最大。而这道题讨厌的地方在于它的状态数是无限的(如果人品够好的话,乙和丙可以一直打一直打不中……)现在详细讲讲。
   
    先分析甲(因为它的命中率100%。不存在无限状态问题)。首先,如果抽到丙先开枪,他肯定会按照最优策略朝天放枪(为什么?看下面!)直到甲和乙分出胜负,因而此时甲的存亡就取决于他和乙谁先开枪,如果甲先(这个几率50%),那么乙立刻就被干掉;或者乙先,则甲有80%被干掉,但如果他活下来,再下一次乙肯定性命不保,因而这一轮中甲的生存概率为
    50%*1(抽到甲先)+50%*20%(抽到乙先)=60%
    接下来就轮到甲和丙了,由于甲刚把乙干掉,肯定是丙先出手(终于不用对天浪费子弹了,呵呵)这下甲有50%被干掉,但同样,丙一旦失手,他自己的死期就到了,因而在这个过程中甲存活的机率为50%
    甲需要熬过两关,他存活的机率为
    60%(打败乙)*50%打败丙=30%
   
    接下来是乙,同样,开始是他和甲的一对一决斗(丙真奸诈啊),这里存活的机率为40%(两人中必有一人活下来,因而100%-60%=40%),麻烦在于他和丙的决斗。
    由于两人都无法保证将对方一次击毙,因而会产生无穷多的情况(想想两个神枪手一对一打了一晚上还没分出胜负……),一步一步来,首先,丙仍然肯定先开枪,50%乙会挂,但如果没有,则乙有80%机率将丙打死,这时一轮下来乙死的概率为50%
    很一般,但每轮有 20%(乙没打中)*50%(丙也没打中)=10%的机率分不出胜负,这时丙会继续开枪,于是和丙的决斗中乙死的概率为
    50%(丙第一轮命中)+10%(第一轮未分胜负)*(50%(丙第二轮命中)+10%(……
    即 0.555555……,转换成分数为5/9
    这样一来,总共乙活的概率为
    40%(打败甲)*55.555%=22.222%

    至于丙就不用算了,1-30%-22.222%=47.778%,最弱的反而最可能活下来,呵呵!
 
    补充:关于最优策略
    首先甲肯定是优先对乙开枪的,因为乙的威胁最大。
    至于乙,出于同样的理由,会先对甲开枪。
    而丙就不一样了,如果他对甲或乙开枪,打死某个人后轮到的剩下一个人就会立刻打他(如果是乙,有80%会挂,是甲就更惨,肯定没活的……),因而丙的最优策略是对天放枪,直到甲和乙某一人打死对方轮到他时再开枪,这样无论面对谁,他都有50%的胜率,而不是开始可怜的0%和<20%……
February 05

久石让-《Piano Story》

久石让的著名钢琴曲专辑,现在已出了三张CD
     三张专辑中,大抵只有第一张我是认真听了的,其中前两首《Resphonia》和《W Nocturn》是我最喜欢的,其中包含的浓烈却又隐忍的感情让我分外感动。这两首曲子都不难,特别是《Resphonia》,非常适合弹琴“没有感情”的同志们找找感觉。因此顺便将曲谱附上。
     (文件是Ove格式的,用Overtrue打开,至于这个,自己去Google吧……)
February 03

YsVI完全壁纸集

YsVI完全壁纸集
    falcon的RPG大作
    感觉上伊苏(Ys)系列和FF是两个极端,极佳的冒险动作系统和相比之下精简得不能再精简的剧情,构成了本代伊苏的全部
    falcon一如既往的画风给游戏增色不少,这里是我收集的壁纸集。做这个的主要原因是网上收到的图大都是打了水印的,很难看。
    于是就自己截了截图,奉上
January 22

冰刃 IceSword 1.20

     冰刃 IceSword 1.20 中文版061022,10月22日修正发布,这是一斩断黑手的利刃,它适用于Windows 2000/XP/2003 操作系统,其内部功能是十分强大,用于查探系统中的幕后黑手-木马后门,并作出处理。可能大家也用过很多类似功能的软件,比如一些进程工具、端口工具,但是现在的系统级后门功能越来越强,一般都可轻而易举地隐藏进程、端口、注册表、文件信息,一般的工具根本无法发现这些“幕后黑手”。IceSword 使用了大量新颖的内核技术,使得这些后门躲无所躲。使用前请详细阅读说明。第一次使用请保存好数据,需要自己承担可能的风险(^_^),特别是多处理器机器的大牛们,谢谢。建议:如果使用新版本异常的同志,请下载冰刃 IceSword 1.12 版本使用。
     PS:如果你想手工练习杀毒的话,这也是个不错的工具………………另外它的禁止新线程创建功能实在太有用了!
 
IceSword永久升级页面: http://www.ttian.net/website/2005/0829/391.html
December 31

中国剩余定理

整理了一下笔记……
 
中国剩余定理(也有叫孙子定理的)主要用于解以下同余方程组:
__
| x mod m1=a1
| x mod m2=a2
| x mod m3=a3
| ……
| x mod mn=an
--
 
    其中方程数可以有很多。其实这个定理所解决的问题大家肯定见过,就是小时候的:有很多人,一组分3人剩两人,一组分4人剩三人……问一共有几人
    需要注意的是,这里的a1 a2 a3 …… 两两互质,它的实现方法如下:
 
    首先,设M=m1*m2*m3……*mn  s(k)=M/mk (1<=k<=n)
    对于一个 x mod mk=ak (1<=k<=n) 必然有b符合 (b*s(k)) mod mk=1 利用扩展的辗转相除法解出b。由于
    (b*s(k)) mod mk=1
    两边乘以ak
    (ak*b*s(k)) mod mk=ak
    则b*s(k)*ak就恰好是对这个k成立的x。

    累加所有k的b*s(k)*ak得到一个数y。我们知道实际上对于y中每一个b*s(k)*ak,如果将他加上 mk,则其mod mk结果是不变的。而其他的所有ak*b*s(k)中都肯定含有mk(看上面,sk是怎么设的?)因而对于任意b*s(k)其累加后相应的 mod mk的结果都是不变的。即累加后得到的y能满足所有k的b*s(k) mod mk=1。

    所以y就是x的一个解,要想求到最小解,只要将y mod M即可。
December 25

关于位运算

作为凌晨那篇文章的补充
    位运算,顾名思义,就是直接在整数的二进制上进行逻辑运算,比如
 
         5 and 3->101 and 011=001->1 即 5 and 3=1
 
    当然,在计算机上述的存储略有不同,以16进制integer为例:
 
         5:0000000000000101 32767:0111111111111111 (第一位作为符号位)
 
    在负数下按绝对值补码存(补码是什么?看下面!)
 
           -1:0000000000000001->1111111111111110->+1->1111111111111111
             即-1:1111111111111111
 
    and or not 自不必多说,这里介绍一下Xor(异或)
    Xor的定义为:a Xor b=True(1)当且仅当 a 和 b 中有且仅有一个为True(1)
               如: 101 Xor 010=111   111 Xor 010=101
    很容易注意到,a Xor b两次后结果仍然是 a ,这一特性可以用于简单加密
    也可以用于数的翻转,比如b=01010,则当a Xor b时,a的 2、4 位就会反转
(0变为1,1变为0)(想想为什么?),比如11001 Xor 01010=10011
   
    附一些特殊的位运算:
 
    把数字右边的连续1变成0(如1010111->1010000) :x and (x-1)
    把右边连续0变成1:x or (x-1)
    把右边第一个0变成1(1011011->1011111): x or (x+1)
    去掉右边第一个1的左边的数:x and (x Xor (x-1)) 或 x and -x

原码,反码,补码

刚才某牛讲了位运算,突然发现我误导了好多人……

数在计算机中的存储:

正数使用原码-即数字的二进制

负数使用的是补码-即原码取反后加1

之所以这样做,完全是因为负数没有0

因此负数的1000000000000001(16位,1代表符号位-负数)其实是-32767

而1000000000000000特殊约定,表示-32768,这样就把负数的范围扩大了1(正数十六位是0-32767吧)

所以A and -A=A and (a Xor (a-1))

December 14

今后的学习计划

某牛列的,突然发现我懂的真少…………
 
信息学与计算机语言的发展(香农与信息论,图灵与图灵机,冯·诺伊曼与逻辑门,机器码,汇编语言,高级语言,面向对象的语言,开源化与多平台)
时间复杂度(渐近时间复杂度的严格定义,NP问题,时间复杂度的分析方法,主定理)
排序算法(平方排序的应用,Shell排序,快速排序,归并排序,时间复杂度下界,线性时间排序,外部排序)
数论(整除,集合论,关系,素数,进位制,辗转相除,扩展的辗转相除,同余运算,^解线性同余方程,中国剩余定理^)
指针(链表,搜索判重,邻接表,开散列,二叉树的表示,多叉树的表示)
按位运算(and,or,xor,shl,shr,一些应用)
图论(图论模型的建立,平面图,欧拉公式与五色定理,求强连通分量,求割点和桥,^欧拉回路,求最短环^,AOV问题,AOE问题,最小生成树的三种算法,最短路的三种算法,标号法,^差分约束系统^,验证二分图,Konig定理,匈牙利算法,^KM算法,稳定婚姻系统^,最大流算法,最小割最大流定理,最小费用最大流算法)
计算几何(平面解几及其应用,向量,点积及其应用,叉积及其应用,判断点是否在多边形内,半平面相交,^求点集的凸包^,最近点对问题,凸多边形的交,Voronoi图与Denaunay三角形剖分,离散化与扫描)
数据结构(广度优先搜索,验证括号匹配,^表达式计算^,递归的编译,Hash表,分段Hash,并查集,^Tarjan算法^,二叉堆,^左偏树^,斜堆,^二项堆^,二叉查找树,AVL,Treap,Splay,2-d树,线段树,二维线段树,矩形树,Trie树,块状链表)
组合数学(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,差分序列与Stirling数,二分求解线性递推方程,生成函数,置换,Polya原理)
概率论(简单概率,概率与平面几何,条件概率,Bayes定理,期望值)
动态规划(最优二叉查找树,树型动规,多叉转二叉,状态压缩类动规,四边形不等式)
字符串处理(KMP,后缀树,有限状态自动机,Huffman编码,简单密码学)
博奕论(Nim取子游戏,博弈树,Shannon开关游戏)
搜索(A*,ID,IDA*,随机调整,遗传算法)
微积分初步(极限思想,导数,积分,定积分,立体解析几何)
综合训练与题目讨论
集训
December 10

巴赫无伴奏小提琴奏鸣曲及组曲--Chaconne

        巴赫无伴奏小提琴奏鸣曲与组曲一共6首,BWVl001—1006,大约于1720年作于德国的克滕。这6首奏鸣曲与组曲,巴赫设计了小提琴表达所能演奏 的一切和弦,使用了几乎不可能演奏的对位技巧。它们构成了巴赫小提琴音乐的颠峰。在巴赫之后,小提琴无伴奏奏鸣曲很少有人再创作,是因为再也无法逾越这座 颠峰
        巴赫无伴奏弦乐作品包括三首小提琴奏鸣曲, 三首组曲(Partita)和六首无伴奏大提琴组曲(suite),都是他任职科登时所作, 在同时期他也创作了许多器乐曲, 这跟那时环境有关, 因为当时并没有合唱团供他使用, 他也不必创作宗教性质音乐.
        这 些作品的创作顺序并没有历史文献可以考证, 如果单纯以推论方式来说, 似乎无伴奏小提琴在先, 这是因为无伴奏小提琴写作在这之前已有许多先例, 而整套无伴奏大提琴作品则以巴赫开先河. 就乐曲组成来看, 小提琴组曲型式非常不统一, 大提琴组曲型式十分一致,似乎是他先在小提琴上熟悉无伴奏弦乐曲写作后再应用于大提琴.
        无伴奏小提琴奏鸣曲及组曲的出版顺序是: 奏鸣曲第一号, 组曲第一号, 奏鸣曲第二号组曲第二号, 奏鸣曲第三号, 组曲第三号如是交错出版. 为何如此呢? 三首组曲的形成为不规则, 但三首奏鸣曲却很明显的为一个统一架构, 是自成一套系统的作品. 是因为调性安排的关系吗? 这六首确实可以找到调性的关联, 但却是组曲, 奏鸣曲自成系统.因此这样的出版顺序可以推论为: 巴赫意欲将这六首统一为一组作品, 避免明显的分为组曲和奏鸣曲两组, 而且如是交错出版也使这六首的安排不致太单调.

这里是其中BWV1004的最后一首:Chaconne(恰空)

地址: http://pickup.mofile.com/0200167641942037 (小提琴原版)
             http://down.cpiano.com/sebastian/competition/16/chaconne.wma (钢琴版,布索尼改编)

以下摘自 http://uwstudentweb.uwyo.edu/H/HUIYUANM/

悲情恰空
   你一定知道巴赫为小提琴而作的d小调第二帕蒂塔(BWV1004),其中最后的“恰空”被人说成“一把琴拉出一个世界”,也就是说,你我在草地上闲坐看 天,或者在雨雪里闷闷无聊的心思,都可以是这音乐的一部分。那十七分钟的漫漫坎坷路,一把琴弓简直如哪吒闹海,而其中每处腾挪,姿态依然风流俊赏。不见 “呕心沥血”,巴赫处处游刃有余,神乎技也。我听了多年,依然在其中迷茫疲惫,因为追不上他的心思。最后弄到乐谱才发现,多年来居然把主题的旋律听错了, 因为那小提琴断奏和弦的时候,铮错之声竟高低难辨。而且,琴声如猎猎红旗般飘动,原来主要是演奏家的念头,巴赫的谱子上却是一片十六分音符整肃得不堪。跟 着谱子好歹看出一二三,那音乐反而更加不能言说,惟有在其中默然啼泣。录音时只有十五岁的美国小女子西拉里。哈恩把它拉得那么洁净清坚,象一面照耀世界的 镜子。一头金色卷发的小小人儿没有少年得志的张扬,惟有一腔素雅情怀谦恭向隅。

        话说巴赫的《恰空》是巨大的宝瓶,你且听人们在其中如何装自己的酒。勃拉姆斯把它改编成专为左手的钢琴练习曲。还有人改编成吉它以及管弦乐。这些曲子中,烟火喧哗是免不了的,我们且看巴赫怎样伸出枝蔓,长成一个“美丽新世界”。

        各种花样的《恰空》中,有意大利钢琴家、作曲家布索尼的钢琴改编曲,我喜欢它到了有些不好意思说的程度:一听就上瘾,反复听下去可以半个晚上都不休止,不 知到底是巴赫还是布索尼让我疯魔。总以为这种喧闹的音乐固然迷人,总有被厌倦的时候,没想到此曲居然如此魅力持久,至今生生不息。甚至在我不听它的时候, 那熊熊热力依旧在心中薪尽火传,悲慨之意在体内吐纳徘徊,竟不可说。音乐中,我有时呆若木鸡,双手叉在裤袋里,跟那张CD封面中弹钢琴的俄国天才小伙子吉 辛一个姿势 ; 要么就是在屋里困兽般遛达,狠狠攥着一把属于自己的斑斓悲欢,简直把手心都硌出血来。向来把自己当成一个理性的人,不会为炫技的东西目迷五色,不过什么叫 沉溺和忘情,这就是了。

        曲子在音响上熊熊蒸腾的时候,斗室里总是象天一样大和亮。一般说来,将小提琴作品向钢琴移植,常常有这种场面:在小提琴可以安然休憩数秒的句尾,倒正是钢 琴大展和声魅力的当儿,它可以轰然作响,永无宁日。在这里,巴赫与布索尼只是轮廓相近而已,布索尼把巴赫当作一个有力的“借口”,而在其中延续着“李斯特 风”,更要在其中突出布索尼—小提琴上的旋律靠钢琴上快速进行的和声刀刻般凸现,双手要象皮球那样轻松弹跳,当然难得有些可恶。中间有一段乐谱读来简单, 却要用琶音奏来,好象巴赫事先预知李斯特、布索尼此等人物,专等他们登场。小提琴上原本单而细的声线此时突然变成立体的雕塑,在钢琴上的刀削斧凿中左右顾 盼。此外,巴赫的小提琴恰空尚有悠闲疏淡之时,而布索尼的钢琴却密不透风,象是不能担当“真空”里的孤独; 巴赫原本从我的肩上掠过,如同一束轻盈的光线,布索尼却是当胸撞来,无可闪回。要说情绪,巴赫一头钻进音符本身,一片苦心始于声音,终于声音,至于他自 己,则衣衫不湿。布索尼倒是无所畏惧,往其中倾倒水火,敞开熬煎着大美大悲哀。从开头的引子起始,他在钢琴上漫卷尘埃,醉醺醺地往复拖曳脚步,漫天的和弦 陨石般一下下地砸着地面,砸出水花。我的眼泪在此被水拦住,啼笑无门,只知这狂欢饱满得溢出杀机,而当下我手无寸铁, 万事皆休。炫技能入此般境界,当然炫的已不仅是技,起码要包括大容量的胸腔,不仅能跟巴赫共振鸣响,又在城府中另开新天—最后索性把巴赫洗干净,或者说, 有一种光线或溪水一样的东西,平分着巴赫和布索尼。唉,布索尼,被人遗忘的布索尼,只留下一些天才的好故事,在图书馆中尘封。

        又是一个七岁首演、九岁开自己作品音乐会的钢琴神童。不仅如此,还是个十足的帅哥,照片上的他“目若点漆”,俊秀如里帕第。他1866年生于意大利,赶上 了浪漫主义盛期的尾巴,稍大一点就听过李斯特,再后来,跟柴科夫斯基、彪罗、格里格、德留斯、马勒都有过交往。然而要命的是,没有童年的神童一直在舞台的 光环里打拼,让观众欢呼鼓掌,可谁知他其实是个内心充满孤单和焦虑的小可怜?这人少小就弹陶西奇改编巴赫的曲子,当然还有大堆李斯特、洪美尔,总之遍历了 钢琴上的声色犬马。难得的是,他不满于钢琴上一双灵活妙手所打造的奇珍丽宝,成年后急不可待地当起作曲家,写管弦乐、钢琴协奏曲和歌剧《图兰朵》以及《浮 士德》(死后由朋友完成),弄电子音乐,著书立说阐述音乐和钢琴新观念—愈老愈“新”,到晚年干脆把舒曼舒伯特肖邦都否定了,只认一个“新”。观念之外, 他倒是乖孩子,待人也好,有个平安的家,有很多朋友和学生。他的书信是细腻动人的文字,还懂绘画,几乎算得上饱学之士。说来说去,他简直是浪漫时期少有的 完美艺术家。奇怪的是,这个在人世间如鱼得水的翩翩君子竟然身后寂寞,活活被命运开了玩笑。也许他写的东西过于“实验”,或者深度不够(而另一个当时的新 新人类马勒可是渐渐大扬其名),所以被忘却。如今人们扳着手指数现代作曲家,常常遗漏这个开山之人。甚至作为钢琴家,他也不如同时代的安东。鲁宾斯坦多少 留下雪泥鸿爪,至今仍然被提及。照片上,少年英俊的他中年后还不是显出松弛衰朽之相。布索尼五十八岁就死了。

        其实布索尼除《恰空》之外还改编了大量巴赫,包括若干为管风琴写的众赞歌和前奏赋格,还有数不胜数的莫扎特,肖邦,《恰空》在居然不在主要作品名单之内。 而我仅仅知道它,而且狠狠迷上它,不知如何摆脱。这首曲子,有时是被作为改编曲的讥嘲对象的,尤其是,这家伙居然以大喧哗介入巴赫的清贞之声,把巴赫隐忍 的表情演成戏剧里的泪与笑,实在是弄俗了他。然而我却大大感念布索尼在《恰空》中漫天飞舞的浓烈情思。而那浸透在旋律里的,步步追逼的疯狂倾诉,在我听来 分明有着冲天的壮烈和“不甘”。这“不甘”,巴赫是没有的,而我偏偏喜欢看到小人物的悲情和无奈。

        钢琴家尼古拉耶娃演奏的《恰空》曾让我惊为天人,然而我现在还是更喜欢年轻的小提琴家西拉里。哈恩和钢琴家吉辛。这些早早出道,至今年轻的天才就象布索尼 一样让人艳羡爱怜。“苏联人”吉辛戴着红领巾时就开音乐会了,那时他已经被当作天才和天使。而这张有《恰空》还有贝多芬舒曼的CD总放在窗前最好拿的地 方,于是我天天看到封面上,长大了的吉辛冷然无笑容的英俊面庞。每天早晨,我在陋室里走动,收拾东西,看天的时候,阳光温暖而迟慢地在CD上留下清影,让 人呆想何时这些人和音都要变作尘土和流水。

November 11

童话里的别墅

套图《童话里的别墅》
     一位水粉画家的套画,梦幻般的房子
     曾几何时,它们也是儿时心中的梦
     ………………

来点音乐

第一帖:为什么要写这个
     我不知道什么音乐是好的,但我知道我喜欢的是它们…………
     诚实的说,我不是一个喜欢听歌的人,因为我总觉得,听歌时心太易被歌词所打扰了。其实,歌曲是一样很尴尬的东西:夹在曲子和诗歌之间。当然也兼有两者的优点:容易理解,易学动听……
     但很不幸,我很喜欢纯音乐而非歌。毕竟这些曲子的意义并非如歌词那般直白,而需要认真听。所以也许在很多人看来,这样的曲子,特别是大多数古典音乐,都被认为是“晦涩而难懂的”
     不过我认为事实并非是这样,所以,会在今后选一些好听又有深度的曲子推荐出来。也许你第一遍听时会不知所云,但只要多听几次,就会沉醉其中的。
     接下来就等下文吧
                                                                                 onion
                                                                               0:04 2006.11.11
November 07

Notepad2

Notepad2
     推荐给OIer们
     相信大家都用过XP的记事本,而这里的notepad2则是专门为程序员提供的一个文本编辑工具,它有着一切程序编辑器所需要的性能:代码高亮(支持C/C++ Pascal Java Html ………………)、() {}  begin/end匹配,自动缩进…………总之作为一个代码编辑器,它是绝对胜任的。
     而它特别的地方在于,相对于目前大多文本编辑器,它的体积非常小,特别是它可以在安装时自动替换掉XP默认的记事本程序(出于兼容性的考虑,最新版本取消了这一功能,需要用户把notepad2.exe改名为notepad.exe并放到\windows\system32下覆盖同名文件),并且界面很简洁(在关闭它的工具栏后,它的界面就和记事本一模一样了!)如果你对自己使用的IDE的代码编辑器不满意的话,这个记事本将是个很好的选择),特别推荐对Freepascal的IDE界面感到不适和正受到Lazarus稳定性困扰的Pascal程序员(其实大多是OIer吧^_^)使用,毕竟在Win窗口下写程序比Dos窗口下好多了
    (给一个下载链接:http://pickup.mofile.com/7157110503843775
 

Onion 的杂物箱

Photo 1 of 37

Onion

Occupation
Location
Interests