主要参考资料:CLJppt。
自动机组成:状态、初始状态、终止状态、状态转移、字符集。
经典图片:
ACADD对应的SAM
对于整个串而言,初始状态(以下简称为init)为ROOT,终止状态集合(以下简称end)为最上方及最右方的那两个写着D的圈(状态既不是字符,也不是子串,在这里把它理解为某个下标更好),所有的状态就是那七个圈,每条实线边代表从一个状态向另一个状态的状态转移。字符集不太重要,在这里为26个字母(暂不区分大小写)。
为便于阅读及写作,下文中,定义均使用
的格式进行标注。当发现有问题时,不妨回到开头再看一眼定义。
例如,上图中,如果当前状态在左下角的A这个圈,通过’D’可以转移到右下角倒数第二个D这个圈,通过’C’可以转移到。。。唯一的C所在的圈。
由于一直用**圈**表述状态实在太麻烦,因此在不引起歧义的前提下,我也会使用某个字符来表示状态。但请时刻注意,状态并非字符。
例如,上图中,从init通过字符串"AC"转移可以到达唯一的那个C。
很明显,有如下函数(伪代码):
trans(s,str)
cur = s;
for i = 0 to len(str) - 1
cur = trans(cur, str[i])
return cur
就是说, t r a n s ( s , s t r ) trans(s,str) trans(s,str)可以看成是一大堆 t r a n s ( s , c h ) trans(s,ch) trans(s,ch)的总和。
(说白了就是把x放到ROOT上开始沿着边跑,如果在end中节点上结束,就说能识别。栗子:我往一颗Trie树中插了字符串"ACADD",那这颗Trie就能识别"ACADD",不能识别"ACA"、"BC"等等奇奇怪怪的字符串)
比如,还是刚才那图,从C开始,"DD"可以被识别(因为 t r a n s ( C trans(C trans(C所在的圈, " D D " ) ∈ e n d "DD") \in end "DD")∈end)
也就是说,当且仅当x是S的后缀时, x ∈ R e g ( S A M ) x \in Reg(SAM) x∈Reg(SAM)。既然能识别所有的后缀,那我们扩展end集合后,SAM也能识别S所有后缀的前缀,即S的子串。
当然我们可以把一个串的所有后缀都插入Trie树,这当然也是后缀自动机,但是时间、空间复杂度都是 O ( n 2 ) O(n^2) O(n2)。
考虑到这样一颗Trie树会有很多重复的字符,因此我们需要最简状态后缀自动机,即状态数(就是需要的圈)最少的后缀自动机。
定义 S T ( s t r ) ST(str) ST(str)为初始状态读入str(以下用“读入%s”表示“通过%s转移”)后到达的状态。
对于一个字符串s,如果 s ∉ F a c s \notin Fac s∈/Fac,那么 S T ( s ) = N U L L ST(s)=NULL ST(s)=NULL(显然)。而如果 s ∈ F a c s \in Fac s∈Fac,那么 S T ( S ) ̸ = N U L L ST(S) \not= NULL ST(S)̸=NULL,因为如果s是S的子串,在s后面加上一些字符就可能变为S的后缀。
考虑朴素的插入,是对每个 s ∈ F a c s \in Fac s∈Fac都新建一个状态(既然对所有后缀建一条链,那对于某个后缀的前缀的最后一个字符,都要新建一个状态,那么可以看作对每个子串新建了一个状态),这样做是 O ( n 2 ) O(n^2) O(n2)的。
考虑对于某个字符串 a a a, S T ( a ) ST(a) ST(a)能识别那些字符串,即从 S T ( a ) ST(a) ST(a)起通过那些字符串可转移到end,即 R e g ( S T ( a ) ) Reg(ST(a)) Reg(ST(a))
x ∈ R e g ( S A M ) x \in Reg(SAM) x∈Reg(SAM),当且仅当 x ∈ S u f x \in Suf x∈Suf
x ∈ R e g ( S T ( a ) ) x \in Reg(ST(a)) x∈Reg(ST(a)),当且仅当 a x ∈ S u f ax \in Suf ax∈Suf
也就是说ax是S后缀,那么x必定也是S后缀。 ∴ R e g ( S T ( a ) ) \therefore Reg(ST(a)) ∴Reg(ST(a))是一些后缀集合。
现在,对于一个状态s,我们只关注 R e g ( s ) Reg(s) Reg(s)。
对于某个子串a,如果 S [ l , r ) = = a S[l,r)==a S[l,r)==a,那么 S T ( a ) ST(a) ST(a)能识别 S u f f i x ( r ) Suffix(r) Suffix(r)。
如果a在S中出现的位置的集合为 { ( l , r ) ∣ S [ l , r ) = = a } = { [ l 1 , r 1 ) , [ l 2 , r 2 ) , . . . , [ l n , r n ) } \{(l,r)|S[l,r)==a\}=\{[l_1,r_1),[l_2,r_2),...,[l_n,r_n)\} {(l,r)∣S[l,r)==a}={[l1,r1),[l2,r2),...,[ln,rn)},那么 R e g ( S T ( a ) ) = { S u f f i x ( r 1 ) , S u f f i x ( r 2 ) , . . . , S u f f i x ( r n ) } Reg(ST(a))=\{Suffix(r_1),Suffix(r_2),...,Suffix(r_n)\} Reg(ST(a))={Suffix(r1),Suffix(r2),...,Suffix(rn)}。
不妨令 R i g h t ( a ) = { r 1 , r 2 , . . . , r n } Right(a)=\{r_1,r_2,...,r_n\} Right(a)={r1,r2,...,rn}。
那么 R e g ( S T ( a ) ) Reg(ST(a)) Reg(ST(a))完全由 R i g h t ( a ) Right(a) Right(a)决定。
又因为我们要使状态数最少,那所有状态s应该与所有 R e g ( s ) Reg(s) Reg(s)一一对应(首先,每个状态s一定能映射到一个Reg集合。然后,如果两个s映射到同一个Reg集合,为什么不把它们合并呢?)。也就是说, R e g ( S T ( a ) ) = = R e g ( S T ( b ) ) ⇔ S T ( a ) = = S T ( b ) Reg(ST(a))==Reg(ST(b)) \Leftrightarrow ST(a)==ST(b) Reg(ST(a))==Reg(ST(b))⇔ST(a)==ST(b)
那么对于两个子串 a , b ∈ F a c a,b \in Fac a,b∈Fac,如果 R i g h t ( a ) = R i g h t ( b ) Right(a)=Right(b) Right(a)=Right(b),那么 R e g ( S T ( a ) ) = R e g ( S T ( b ) ) Reg(ST(a))=Reg(ST(b)) Reg(ST(a))=Reg(ST(b)),即 S T ( a ) = S T ( b ) ST(a)=ST(b) ST(a)=ST(b)。
所以一个状态s,可以由所有 R i g h t Right Right集合是 R i g h t ( s ) Right(s) Right(s)的字符串从init转移到达。记这些字符串为 S u b ( s ) Sub(s) Sub(s)
不妨设 r ∈ R i g h t ( s ) r \in Right(s) r∈Right(s),那么如果再给出一个合法的子串长度len,那么这个子串就是 S [ r − l e n , r ) S[r-len,r) S[r−len,r)。即给定某个合法的(在这里指存在某个状态s的Right集合等于它)Right集合后,再给出一个合法的长度就可以确定子串了。
考虑对于一个Right集合,如果对于串长为 l e n = l o r l e n = r len=l\ or\ len=r len=l or len=r的子串是合法的,那么对于 l e n ∈ [ l , r ] len \in [l,r] len∈[l,r]的子串也一定合法。
一些说明:设Right集合 R 1 R_1 R1中有一元素 s u b s t r i n g _ r 1 substring\_r_1 substring_r1,在 l e n = l o r l e n = r ( l < = r ) len=l\ or\ len=r(l<=r) len=l or len=r(l<=r)的情况下 R i g h t ( S [ s u b s t r i n g _ r 1 − l e n , s u b s t r i n g _ r 1 ) ) = R 1 Right(S[substring\_r_1-len,substring\_r_1))=R_1 Right(S[substring_r1−len,substring_r1))=R1,那么情况如图。
很显然,对于 l e n ∈ [ l , r ] , S [ s u b s t r i n g _ r 1 − l e n , s u b s t r i n g _ r 1 ) len \in [l,r],S[substring\_r_1-len,substring\_r_1) len∈[l,r],S[substring_r1−len,substring_r1),都可以且仅可以识别R1中元素开始的后缀。而对于 l e n > r len>r len>r,只能保证仅可以;对于 l e n < l len<l len<l,只能保证可以。因此在 l e n ∉ [ l , r ] len \notin [l,r] len∈/[l,r]情况下,不保证 R i g h t ( S [ s u b s t r i n g _ r 1 − l e n , s u b s t r i n g _ r 1 ) ) = R 1 Right(S[substring\_r_1-len,substring\_r_1))=R_1 Right(S[substring_r1−len,substring_r1))=R1。
所以对于一个Right集,合法的长度只能存在于一个区间内。
不妨设对于一状态s,合法的区间长度为 [ M i n ( s ) , M a x ( s ) ] [Min(s),Max(s)] [Min(s),Max(s)]
考虑两个状态a,b,各自的Right集合分别为 R a , R b R_a,R_b Ra,Rb。
假设 R a ∩ R b ̸ = ∅ R_a \cap R_b \not= \emptyset Ra∩Rb̸=∅,设 r ∈ R a ∩ R b r \in R_a \cap R_b r∈Ra∩Rb。
由于 S u b ( a ) , S u b ( b ) Sub(a),Sub(b) Sub(a),Sub(b)不会有交集( t r a n s ( i n i t , s t r ) trans(init,str) trans(init,str)只能到达一个状态),所以 [ M i n ( a ) , M a x ( a ) ] [Min(a),Max(a)] [Min(a),Max(a)]和 [ M i n ( b ) , M a x ( b ) ] [Min(b),Max(b)] [Min(b),Max(b)]也不可能有交集(不然的话以r为结束的某些子串会既到达a,又到达b)。
不妨设 M a x ( a ) < M i n ( b ) Max(a)<Min(b) Max(a)<Min(b),那么 S u b ( a ) Sub(a) Sub(a)中所有串长度都小于 S u b ( b ) Sub(b) Sub(b)中的串。由于 S u b ( a ) , S u b ( b ) Sub(a),Sub(b) Sub(a),Sub(b)中的串都可以接上 S u f f i x ( r ) Suffix(r) Suffix(r)( R i g h t Right Right定义),那么 S u b ( a ) Sub(a) Sub(a)中串必定是 S u b ( b ) Sub(b) Sub(b)中串的后缀。
于是 S u b ( b ) Sub(b) Sub(b)中某串出现的位置上, S u b ( a ) Sub(a) Sub(a)中某串也必然能在这里出现。因此 R b ⊂ R a R_b \subset R_a Rb⊂Ra。又 R a ̸ = R b R_a \not= R_b Ra̸=Rb,所以 R b ⫋ R a R_b \subsetneqq R_a Rb⫋Ra。
于是, F a c Fac Fac中任意两串的 R i g h t Right Right集合,要么不相交,要么一个是另一个的真子集,要么完全相同。
那么,如果我们把所有的 R i g h t Right Right集合组成集合,我们可以把他们组织成一个树的结构(如上图)。我们把它叫做Parent树。
很明显,这棵树叶节点有n个,而每个非叶子节点至少有两个孩子(不存在只有一个孩子),易证树的大小为 O ( n ) O(n) O(n)级别(显然??)。
令一个状态s,令 f a = P a r e n t ( s ) fa=Parent(s) fa=Parent(s)代表唯一的那个 R i g h t Right Right集合是 R i g h t ( s ) Right(s) Right(s)父节点对应的集合的状态(这句话有点绕口,多读)。那么自然有 R i g h t ( s ) ⫋ R i g h t ( f a ) Right(s) \subsetneqq Right(fa) Right(s)⫋Right(fa),并且 R i g h t ( f a ) Right(fa) Right(fa)的大小是其中最小的。
考虑 l e n ( S u b ( s ) ) len(Sub(s)) len(Sub(s))和 l e n ( S u b ( f a ) ) len(Sub(fa)) len(Sub(fa))。则 l e n ( S u b ( s ) ) ∈ [ M i n ( s ) , M a x ( s ) ] len(Sub(s)) \in [Min(s),Max(s)] len(Sub(s))∈[Min(s),Max(s)]。考虑 M i n ( s ) − 1 Min(s)-1 Min(s)−1为什么不可以,因为长度变短,限制条件变少,于是可出现的位置多了。假设 R i g h t ( s ) = R 1 Right(s)=R_1 Right(s)=R1,还是这幅图:
这是 M i n ( s ) − 1 Min(s)-1 Min(s)−1的情况:
显然,这个变大后的 R i g h t Right Right集是所有包含 R 1 R_1 R1的集合中最小的一个。因此它就是 R i g h t ( f a ) Right(fa) Right(fa)。因此, M a x ( f a ) = M i n ( s ) − 1 Max(fa)=Min(s)-1 Max(fa)=Min(s)−1。(L就是 M i n ( s ) Min(s) Min(s))
我们已经证明了状态数是 O ( n ) O(n) O(n)的,为证明这是一个线性结构,还需证明它状态转移数也是 O ( n ) O(n) O(n)的。
s->ch[a] = t
)。自己看CLJ课件去!
显然法:状态是 O ( n ) O(n) O(n)的,每个状态最多连出26条边,状态很显然是 O ( n ) O(n) O(n)的。
对于一个状态s,它的 R i g h t ( s ) = { r 1 , r 2 , . . . , r n } Right(s)=\{r_1,r_2,...,r_n\} Right(s)={r1,r2,...,rn},现有s->ch[a]=t
,考虑t的 R i g h t Right Right集合,由于t比s多一个字符,因此它限制更多, R i g h t ( t ) = { r i + 1 ∣ S [ r i ] = = c } Right(t)=\{r_i+1|S[r_i]==c\} Right(t)={ri+1∣S[ri]==c}。
终于可以把这张大图下放了。
例如,对这个"ACADD"的SAM,若s = root->ch['a']
,t = s->ch['d']
,那么 R i g h t ( s ) = { 1 , 3 } Right(s)=\{1,3\} Right(s)={1,3}(对应的后缀为"DD",“ACDD”), R i g h t ( t ) = { 4 } Right(t)=\{4\} Right(t)={4}(对应的后缀为"D"),显然符合前面所说规则。(记住下标从0开始)
同时,如果s出发有标号为x的边,那么 P a r e n t ( s ) Parent(s) Parent(s)出发也一定有(因为 s ⫋ P a r e n t ( s ) s \subsetneqq Parent(s) s⫋Parent(s))。
令 f = P a r e n t ( s ) f=Parent(s) f=Parent(s)。那么 R i g h t ( t r a n s ( s , c ) ) ⊂ R i g h t ( t r a n s ( f , c ) ) Right(trans(s,c)) \subset Right(trans(f,c)) Right(trans(s,c))⊂Right(trans(f,c))(因为 s ⫋ f s \subsetneqq f s⫋f)。
一个显然的推论是 M a x ( t ) > M a x ( s ) Max(t)>Max(s) Max(t)>Max(s)。(对t而言,如果不考虑 M i n ( t ) Min(t) Min(t),那么 l e n = M a x ( s ) + 1 len=Max(s)+1 len=Max(s)+1显然合适)
我们使用在线方法构造,即每次添加一个字符,使得当前SAM变成包含这个新字符的SAM(即,先构造 S A M ( S [ 0 , i ) SAM(S[0,i) SAM(S[0,i),再构造 S A M ( S [ 0 , i + 1 ) SAM(S[0,i+1) SAM(S[0,i+1))。
令当前字符串为T,新字符为x,令 l e n ( t ) = L len(t)=L len(t)=L。
在 S A M ( T ) → S A M ( T x ) SAM(T) \rightarrow SAM(Tx) SAM(T)→SAM(Tx)的过程中,这个SAM可以多识别一些原串S的子串,这些子串都是Tx的后缀。
Tx的后缀,就是在T的后缀后面接上字符x。
考虑所有可以表示T后缀(即, R i g h t Right Right集中包含L)的节点 v 1 , v 2 , v 3 , . . . v_1,v_2,v_3,... v1,v2,v3,...。
由于必然存在一个 R i g h t ( p ) = L Right(p)=L Right(p)=L的状态p( p = S T ( T ) p=ST(T) p=ST(T)),那么由于 v 1 , v 2 , v 3 . . . v_1,v_2,v_3... v1,v2,v3...的 R i g h t Right Right集合中都含有L,那么它们在 P a r e n t Parent Parent树中必然全是p的祖先(由于 P a r e n t 树 的 性 质 Parent树的性质 Parent树的性质)。那么可以使用 P a r e n t Parent Parent函数得到它们。
同时我们添加一个字符x后,令 n p = S T ( T x ) np=ST(Tx) np=ST(Tx),则此时 R i g h t ( n p ) = L + 1 Right(np)=L+1 Right(np)=L+1。
不妨设 v 1 = p , v 2 = P a r e n t ( v 1 ) , v 3 = P a r e n t ( v 2 ) , . . . , v k = P a r e n t ( v k − 1 ) = r o o t v_1=p,v_2=Parent(v_1),v_3=Parent(v_2),...,v_k=Parent(v_{k-1})=root v1=p,v2=Parent(v1),v3=Parent(v2),...,vk=Parent(vk−1)=root( R i g h t ( r o o t ) = [ 0 , L ] Right(root)=[0,L] Right(root)=[0,L])。
考虑其中一个v的 R i g h t Right Right集合 { r 1 , r 2 , . . . , r n } ( r n = L ) \{r_1,r_2,...,r_n\}(r_n=L) {r1,r2,...,rn}(rn=L),那么如果v->ch[x]=nv
,则 R i g h t ( n v ) = { r i + 1 ∣ S [ r i ] = = x } Right(nv)=\{r_i+1\ |\ S[r_i]==x\} Right(nv)={ri+1 ∣ S[ri]==x}。同时,如果v->ch[x]=NULL
,那么v的 R i g h t Right Right集合内就没有一个 r i r_i ri使得 S [ r i ] = = x S[r_i]==x S[ri]==x(先不考虑 v n v_n vn)。
又由于 R i g h t ( v 1 ) ⫋ R i g h t ( v 2 ) ⫋ R i g h t ( v 3 ) . . . Right(v_1) \subsetneqq Right(v_2) \subsetneqq Right(v_3)... Right(v1)⫋Right(v2)⫋Right(v3)...,那么如果v[i]->ch[x]!=NULL
,那么v[j]->ch[x]
也一定不是NULL( j > i j>i j>i)。
对于v->ch[x]==NULL
的v,它的 R i g h t Right Right集合内只有 r n r_n rn满足要求,所以根据之前提到的规则,使得v->ch[x]=np
。
令 v p v_p vp为 v 1 , v 2 , v 3 , . . . v_1,v_2,v_3,... v1,v2,v3,...中第一个ch[x]!=NULL
的状态。
考虑 R i g h t ( v p ) = { r 1 , r 2 , . . . , r n } Right(v_p)=\{r_1,r_2,...,r_n\} Right(vp)={r1,r2,...,rn},令 t r a n s ( v p , x ) = q trans(v_p,x)=q trans(vp,x)=q。
那么 R i g h t ( q ) = { r i + 1 ∣ S [ r i ] = = x } Right(q)=\{r_i+1|S[r_i]==x\} Right(q)={ri+1∣S[ri]==x}(不考虑r_n)。
但是,我们不一定能直接在 R i g h t ( q ) Right(q) Right(q)中插入 L + 1 L+1 L+1(暂且不管如何插入)。
如果在 R i g h t ( q ) Right(q) Right(q)中直接插入 L + 1 L+1 L+1,由于限制条件增加,可能使 M a x ( q ) Max(q) Max(q)变小。
放图:
红色是结束在 R i g h t ( v p ) Right(v_p) Right(vp)位置上,长度为 M a x ( v p ) Max(v_p) Max(vp)的串,蓝色为结束在 R i g h t ( q ) Right(q) Right(q)位置上,长度为 M a x ( q ) Max(q) Max(q)的串。
于是强行做的话, M a x ( q ) Max(q) Max(q)变小就很显然了。
当然,如果 M a x ( q ) = = M a x ( v p ) + 1 Max(q)==Max(v_p)+1 Max(q)==Max(vp)+1,就不会有这样的问题(这个很显然,把上图蓝色串开头的A改成B就行了),直接插入即可。
怎么插入? P a r e n t ( n p ) = q Parent(np)=q Parent(np)=q。
证明:很显然 R i g h t ( n p ) ⫋ R i g h t ( q ) Right(np) \subsetneqq Right(q) Right(np)⫋Right(q)(q不会是 S T ( T ) ST(T) ST(T), L + 1 ∈ R i g h t ( q ) L+1 \in Right(q) L+1∈Right(q)),所以只需证明 R i g h t ( q ) Right(q) Right(q)是所有包含 R i g h t ( n p ) Right(np) Right(np)的集合中大小最小的。反证法,假设存在一个包含 R i g h t ( n p ) Right(np) Right(np)的集合 R i g h t ( f ) Right(f) Right(f),且 P a r e n t ( f ) = q Parent(f)=q Parent(f)=q,那么 M i n ( f ) = M a x ( q ) + 1 Min(f)=Max(q)+1 Min(f)=Max(q)+1,于是当 l e n = M a x ( q ) + 1 len=Max(q)+1 len=Max(q)+1时, l e n ∈ [ M i n ( f ) , M a x ( f ) ] len \in [Min(f),Max(f)] len∈[Min(f),Max(f)]。设 R i g h t ( q ) = { r 1 , r 2 , . . . , r k , . . . , r n } Right(q)=\{r_1,r_2,...,r_k,...,r_n\} Right(q)={r1,r2,...,rk,...,rn}, R i g h t ( f ) = { r 1 , r 2 , . . . , r k , r n } Right(f)=\{r_1,r_2,...,r_k,r_n\} Right(f)={r1,r2,...,rk,rn},看图。
如果真是这样, R i g h t ( p ) Right(p) Right(p)不会是图上的 R i g h t ( p ) Right(p) Right(p),其中上方左数第一条边不应该存在(左数第三条可能也不存在)。因为只需要第2、4条边就可以使
p->ch[x]!=NULL
了,而这时第一条边显然还没有(加入它后Right§会变小)。于是产生矛盾,由此命题得证。
在2的情况下再减去一些限制条件又会怎样呢?
比如,现在没有了 M a x ( q ) = = M a x ( v p ) + 1 Max(q)==Max(v_p)+1 Max(q)==Max(vp)+1。
这个条件这么好,没有了岂不是可惜?
没有条件,创造条件。
上图中,红色为 R i g h t ( v p ) Right(v_p) Right(vp),蓝色为 R i g h t ( q ) Right(q) Right(q),绿色为 R i g h t ( n q ) Right(nq) Right(nq)。
我们把q拆成两份:新建一个节点nq,使 R i g h t ( n q ) = R i g h t ( q ) ∪ { L + 1 } Right(nq)=Right(q) \cup \{L+1\} Right(nq)=Right(q)∪{L+1}。这时,我们可以证明 M a x ( n q ) = M a x ( v p ) + 1 Max(nq)=Max(v_p)+1 Max(nq)=Max(vp)+1,由于证法与上面那个证明非常类似所以不说了。
于是 R i g h t ( q ) , R i g h t ( n p ) ⫋ R i g h t ( n q ) Right(q),Right(np) \subsetneqq Right(nq) Right(q),Right(np)⫋Right(nq),且 R i g h t ( n p ) + R i g h t ( q ) = R i g h t ( n q ) Right(np)+Right(q)=Right(nq) Right(np)+Right(q)=Right(nq)(图上可以看出)。于是, P a r e n t ( n p ) = P a r e n t ( q ) = P a r e n t ( n q ) Parent(np)=Parent(q)=Parent(nq) Parent(np)=Parent(q)=Parent(nq)。
又由于 M i n ( q ) = M i n ( n q ) Min(q)=Min(nq) Min(q)=Min(nq)(感性认知),所以 P a r e n t ( n q ) = P a r e n t ( q ) Parent(nq)=Parent(q) Parent(nq)=Parent(q)(原来的)。
然后, t r a n s ( n q , x ) = t r a n s ( q , x ) trans(nq,x)=trans(q,x) trans(nq,x)=trans(q,x)(因为多出来的那个点再转移过程中没有用)。
接着,我们回头考虑 v 1 , v 2 , . . . , v k v_1,v_2,...,v_k v1,v2,...,vk这一序列。其中最早在 v p v_p vp处ch[x]!=NULL
。于是乎只有一段 v p , . . . , v e v_p,...,v_e vp,...,ve(不是 v p , . . . , v k v_p,...,v_k vp,...,vk,因为 v e v_e ve之后 R i g h t ( v − > c h [ x ] ) Right(v->ch[x]) Right(v−>ch[x])会更大)通过x边转移到q。那么现在把这一段的 t r a n s ( ∗ , x ) trans(*,x) trans(∗,x)改为nq既可。
//源文件竟有4000字了?