题目
思路
暴搜
数学题:已知 T ( n ) = O ( T(n)=\mathcal O( T(n)=O( 可以过 ) ) ),并且 T ( n ) T(n) T(n) 最好情况下是 O ( log n ) \mathcal O(\log n) O(logn),求 T ( n ) T(n) T(n) 。
普通的爆搜
看到标题,你肯定不屑一顾。“哼,爆搜谁不会啊?”
巧了,我还真不会。 但是 有位大佬就这么过了!
思路是暴力干掉 lcp \text{lcp} lcp 。没错,暴力,两个指针一起自加的那种。 L O J \tt{LOJ} LOJ 提交记录 。
带劲的爆搜
干掉 lcp \text{lcp} lcp?我记得 lcp \text{lcp} lcp 可以 哈希加二分!想看的点这里。
动态规划
“一切暴搜的本质都是动态规划,只是没有记忆化。”——沃兹基硕德
森破的动态规划
f
(
i
,
j
)
f(i,j)
f(i,j) 表示,
S
S
S 的前
i
i
i 个字符与
T
T
T 的前
j
j
j 个字符最少需要操作多少次才能相同。那么转移就很简单了,删除
、插入
、修改
三种操作就对应着
f
(
i
−
1
,
j
)
,
f
(
i
,
j
−
1
)
,
f
(
i
−
1
,
j
−
1
)
f(i-1,j),f(i,j-1),f(i-1,j-1)
f(i−1,j),f(i,j−1),f(i−1,j−1)
当然,也有可能这个字符不修改。所以很容易写出
f
(
i
,
j
)
=
min
{
f
(
i
−
1
,
j
)
+
1
,
f
(
i
,
j
−
1
)
+
1
,
f
(
i
−
1
,
j
−
1
)
+
[
S
i
≠
T
j
]
}
f(i,j)=\min\{f(i{\rm-}1,j){\rm+}1,f(i,j{\rm-}1){\rm+}1,f(i{\rm-}1,j{\rm-}1){\rm+}[S_i\ne T_j]\}
f(i,j)=min{f(i−1,j)+1,f(i,j−1)+1,f(i−1,j−1)+[Si=Tj]}
这是 O ( n 2 ) \mathcal O(n^2) O(n2) 的,恐怕过不了吧?
牛叉的动态规划
为什么它这么慢呢?——我们只需要操作次数不超过八的个数!然鹅我们全部都求了。
可以使用数学归纳法证明:
f
(
x
,
y
)
≥
∣
x
−
y
∣
f(x,y)\ge|x-y|
f(x,y)≥∣x−y∣
只要注意到代价是 max ( x − i − 1 , y − j − 1 ) \max(x-i-1,y-j-1) max(x−i−1,y−j−1),读者自证不难。
于是我们改进一下,只需要存 y − x y-x y−x 的值和 x x x 即可。就是说,用 g ( x , Δ x ) g(x,\Delta x) g(x,Δx) 表示原来的 f ( x , x + Δ x ) f(x,x+\Delta x) f(x,x+Δx) 。根据上面的结论, Δ x ∈ [ − 8 , 8 ] \Delta x\in[-8,8] Δx∈[−8,8] 。
于是复杂度优化到了 O ( 16 n ) \mathcal O(16n) O(16n),还是挺慢的。
题外话:千万小心, f ( i , j ) f(i,j) f(i,j) 不具有任何单调性。简单的例子是 S = a b S=ab S=ab 与 T = c b T=cb T=cb,此时你会发现 f ( 1 , 2 ) = 2 > f ( 2 , 2 ) = 1 f(1,2)=2>f(2,2)=1 f(1,2)=2>f(2,2)=1 。
D e v i l \sf Devil Devil 的动态规划
怎么变快?——把 8 \color{black}{8} 8 放进状态定义里!
没错,就像很多动态规划优化一样,用 h ( i , Δ x ) h(i,\Delta x) h(i,Δx) 表示 使 f ( x , Δ x ) = i f(x,\Delta x)=i f(x,Δx)=i 的最大的 x x x 。
为什么有最大最好这一点?因为这样可以早点匹配完。这个单调性是正确的,容易证明。
我们考虑一下三种操作:
- 插入:这将导致 Δ x \Delta x Δx 增大 1 1 1(原本是 t t t 对应 t 0 t_0 t0,在 t t t 后面插入一个字符对应了 t 0 + 1 t_0+1 t0+1,于是 t + 1 t+1 t+1 对应 t 0 + 2 t_0+2 t0+2,自然 Δ x \Delta x Δx 增大 1 1 1)。
- 删除:类似的,这会导致 Δ x \Delta x Δx 减少 1 1 1;同时它也让已经匹配的 x x x,即 h ( i , Δ x ) h(i,\Delta x) h(i,Δx),增大了 1 1 1(因为它被删掉了啊)。
- 替换: Δ x \Delta x Δx 不变; h ( i , Δ x ) h(i,\Delta x) h(i,Δx) 加 1 1 1 。
然后,我们用
S
A
\tt SA
SA 搞定
l
c
p
\rm lcp
lcp 即可。复杂度
O
(
N
log
N
+
8
2
⋅
n
2
)
\mathcal O(N\log N+8^2\cdot n^2)
O(NlogN+82⋅n2) 。不过排行榜第一是暴力求
l
c
p
\sout{\;\rm lcp\;}
lcp的?
代码
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 1000005;
int sa[MaxN], rnk[MaxN<<1];
int tmp[MaxN<<1], buc[MaxN];
void collect(int n,int m){
memset(buc,0,m<<2);
for(int i=0; i!=n; ++i)
++ buc[rnk[i]];
for(int i=1; i!=m; ++i)
buc[i] += buc[i-1];
}
void getSA(const int a[],int n){
memset(tmp+n,-1,n<<2);
memcpy(rnk,a,n<<2);
int m = 26; collect(n,m);
for(int i=0; i!=n; ++i)
sa[--buc[rnk[i]]] = i;
if(m == n) ++ m; // special judge
for(int w=1,p=0; m!=n; w<<=1,m=p,p=0){
for(int i=n-w; i!=n; ++i)
tmp[p ++] = i;
for(int i=0; i!=n; ++i)
if(sa[i] >= w)
tmp[p ++] = sa[i]-w;
collect(n,m);
for(int i=n-1; ~i; --i)
sa[--buc[rnk[tmp[i]]]] = tmp[i];
memcpy(tmp,rnk,n<<2);
rnk[sa[0]] = 0; p = 1;
for(int i=1; i!=n; ++i)
if(tmp[sa[i]] != tmp[sa[i-1]]||
tmp[sa[i]+w] != tmp[sa[i-1]+w])
rnk[sa[i]] = p ++;
else rnk[sa[i]] = p-1;
}
}
int heit[MaxN];
void getHeit(const int a[],int n){
for(int i=0,k=0; i!=n; ++i){
if(k) -- k; if(!rnk[i]) continue;
int j = sa[rnk[i]-1], p = max(i,j);
for(; k+p!=n&&a[i+k]==a[j+k]; ++k);
heit[rnk[i]] = k; // length
}
}
namespace RMQ{
int st[20][MaxN], logtwo[MaxN];
void build(int n){
logtwo[0] = -1;
for(int i=1; i!=n; ++i){
st[0][i] = heit[i];
logtwo[i] = logtwo[i>>1]+1;
}
for(int j=0; (2<<j)<n; ++j)
for(int i=1; i+(2<<j)<=n; ++i)
st[j+1][i] = min(st[j][i],st[j][i+(1<<j)]);
}
int query(int l,int r){
if((l=rnk[l]) > (r=rnk[r])) swap(l,r);
++ l; int k = logtwo[r-l+1];
return min(st[k][l],st[k][r-(1<<k)+1]);
}
}
const int K = 8;
int dp[2][K<<1|1];
int S[MaxN]; char T[MaxN];
string str[200]; int pos[201];
int work(int x,int y){
int len = str[x].length();
int lent = str[y].length();
if(lent < len-K || len+K < lent)
return K+1; // not in the range
/* do dp transfer */;
memset(dp,-0x7f,2*(K<<1|1)<<2);
dp[0][K] = RMQ::query(pos[x],pos[y]); // [0,dp)
if(dp[0][K] >= len && len == lent) return 0;
dp[0][K] = min(dp[0][K],min(len,lent));
for(int i=1; i<=K; ++i){
int fr = (i&1)^1;
for(int j=-K; j<=K; ++j){
int t = dp[fr][K+j]+1; // modify
if(j != -K) // insert
t = max(t,dp[fr][K+j-1]);
if(j != K) // erase
t = max(t,dp[fr][K+j+1]+1);
if(t+j < 0){
dp[fr^1][K+j] = -MaxN;
continue; // undefined
}
if(t < len && t+j < lent) // not matched all
t += RMQ::query(pos[x]+t,pos[y]+t+j);
dp[fr^1][K+j] = min(t,min(len,lent-j));
}
if(dp[fr^1][K+lent-len] == len)
return i; // finished
}
return K+1; // not in the range
}
int ans[K+2];
int main(){
int n = readint();
for(int i=0; i!=n; ++i){
scanf("%s",T);
int lent = strlen(T);
T[lent] = 0; str[i] = T;
pos[i+1] = pos[i]+lent;
for(int j=0; j!=lent; ++j)
S[pos[i]+j] = T[j]-'a';
}
getSA(S,pos[n]), getHeit(S,pos[n]);
RMQ::build(pos[n]);
for(int i=1; i!=n; ++i)
for(int j=0; j!=i; ++j)
++ ans[work(i,j)];
for(int i=1; i<=K; ++i)
printf("%d ",ans[i]);
return 0;
}