Chris 小时候一直想玩爵士乐。他现在长大了,但仍然不会演奏任何乐器。不过,他有一个绝妙的想法,要开发一种新型的即兴演奏系统。更重要的是,Bytelander 教授认为 Chris 的程序——即兴演奏 (Improvisation)——可能是一个不错的博士论文课题!唯一的问题是实现这个算法,这对 Chris 来说太复杂了。
该算法的核心思想是重用现有的即兴演奏来创作新的即兴演奏。每段即兴演奏都有其特定的类型,即适合于固定的和弦序列。即兴演奏算法会对一段适合于某个和弦序列的即兴演奏进行调整,使其在修改后适合于另一个和弦序列。
为了理解该算法,需要一些额外的概念:
首先,有 12 个不同的音符,定义了以下经典顺序:
B 音符后面是 C(经典顺序是“周期性”的)。标有升号 (#) 的音符也可以标有降号 (♭)。
为了标记即兴演奏中的停顿,还需要停顿的概念。停顿将用 _ 符号标记。
有不同类型的和弦。每个和弦都有一个调式 (mode) 和一个根音 (prime)。根音只是一个单一的音符。共有六种调式:maj7, min7, dim, 7, 7♭9 和 7#9。对于给定的(根音,调式)对,可以定义适合该和弦的音符。为此,我们为每种调式定义其音阶 (scale),即一组 $S \subseteq \{0, 1,\ldots, 11\}$,表示音符相对于根音在经典顺序中的偏移量。例如,如果根音是 D,音阶是 $S = \{0, 2, 4\}$,那么适合的音符集合就是 {D, E, F#}。所有调式的音阶如下:
| 调式 | 音阶 |
|---|---|
| maj7 | 0, 2, 4, 7, 9, 11 |
| min7 | 0, 2, 3, 5, 7, 9, 10 |
| dim | 0, 2, 3, 5, 6, 8, 9, 11 |
| 7 | 0, 2, 4, 7, 9, 10 |
| 7♭9 | 0, 1, 3, 4, 6, 7, 9, 10 |
| 7#9 | 0, 1, 3, 4, 6, 8, 10 |
例如,Cmaj7 和弦的音阶是 {C, D, E, G, A, B}(和弦被标识为根音和调式写成一个单词)。
即兴演奏是音符和/或停顿的有限序列。
对于给定的音符 $x$,最接近且适合和弦 $A$ 的音符是序列 $x$, next($x$), prev($x$), next(next($x$)), prev(prev($x$)), ... 中第一个适合 $A$ 的元素。next($x$) 表示经典顺序中 $x$ 之后的下一个音符,prev($x$) 表示经典顺序中 $x$ 之前的音符。
即兴演奏算法可以用伪代码表示:
Algorithm's input: sequence of accords P, improvisation I,
length of the sequence of accords m, length of the improvisation n
Result: improvisation J
Variables: integer i, integer pJ := sequence of length n containing only pauses
p := 1
for i := 1 to n do begin
if I[i] <> pause then begin
J[i] := nearest note to I[i] which fits to P[p]
if i mod 4 = 0 then begin
p := p + 1
if p > m then p := 1
end
end
end
实现即兴演奏算法。编写一个程序,该程序:
- 从标准输入读取和弦序列和即兴演奏,
- 根据即兴演奏算法,找到适合给定和弦的即兴演奏,
- 将结果写入标准输出。
输入格式
第一行包含一个整数 $m$,$1 \le m \le 100\,000$。第二行包含一个和弦序列,写为 $m$ 个和弦,以空格分隔。第三行包含一个整数 $n$,$1 \le n \le 100\,000$。第四行包含 $n$ 个音符和/或停顿,以空格分隔,代表一段即兴演奏。
输出格式
输出应为一行,包含 $n$ 个音符和/或停顿,代表生成的即兴演奏,以空格分隔。
说明:当显示一个有两种不同表示形式的音符时,需要根据算法伪代码第 5 行的和弦 $P[p]$ 使用相应的表示形式:如果和弦的根音带有降号,则应使用带降号的表示形式。否则,应使用带升号的表示形式。
样例
输入 1
2 Gb7 Bb7 12 C C# D D# E F _ F# G _ G# A
输出 1
Db Db Eb Eb F F _ G Ab _ Ab Bb