[LOJ6182]说无可说

题目

传送门 to LOJ

思路

暴搜

数学题:已知 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(i1,j),f(i,j1),f(i1,j1)

当然,也有可能这个字符不修改。所以很容易写出
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(i1,j)+1,f(i,j1)+1,f(i1,j1)+[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)xy

只要注意到代价是 max ⁡ ( x − i − 1 , y − j − 1 ) \max(x-i-1,y-j-1) max(xi1,yj1),读者自证不难。

于是我们改进一下,只需要存 y − x y-x yx 的值和 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+82n2)不过排行榜第一是暴力求    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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值