2021杭电多校第五场 1004 Another String

2021杭电多校第五场

Another String

题目链接

题意

给定一个长度为n的字符串S,和一个正整数k

如果两个长度的相同的字符串(a,b)满足 a [ i ] ≠ b [ i ] a[i] \neq b[i] a[i]=b[i] i i i的个数不超过k,则称两个字符串满足k−matching

对于一个分割线t,将字符串S分成两个子串 A = S [ 1 , t ] ,   B = S [ t + 1 , n ] A=S[1, t],\ B=S[t+1,n] A=S[1,t], B=S[t+1,n]​。

A的子串和B子串中有多少对满足k−matching

求出分割线 t ∈ [ 1 , n − 1 ] t\in [1, n-1] t[1,n1]​的所有值。

思路

定义 F [ i ] [ j ] F[i][j] F[i][j]​​为 A A A​​以 i i i​​开头的子串 B B B​​以 j j j​​开头的子串 满足k−matching的个数(也等于最长的长度),

则对于每一个分割线t,都要求出 ∑ i = 1 t ∑ j = t + 1 n m i n ( F [ i ] [ j ] , t − i + 1 ) \sum_{i=1}^{t}\sum_{j=t+1}^{n}min(F[i][j], t-i+1) i=1tj=t+1nmin(F[i][j],ti+1)​​。( t − i + 1 t-i+1 ti+1​​​是因为左边的子串不能超过分割线t)

首先考虑如何求出 F [ i ] [ j ] F[i][j] F[i][j]

  • 暴力求解:对于每一个 ( i , j ) (i,j) (i,j)都求一遍 F [ i ] [ j ] F[i][j] F[i][j],复杂度为 O ( n 3 ) O(n^3) O(n3)

    暴力会导致重复比较,如下图:

在这里插入图片描述

  • 通过枚举L和R之差t(R=L+t),然后用双指针来计算L和R的len(满足k-mat的最大长度)是多少,细节可以看代码中的注释,这样做的复杂度就是 O ( n 2 ) O(n^2) O(n2)

再考虑如何计算出每一个t对应的 ∑ i = 1 t ∑ j = t + 1 n m i n ( F [ i ] [ j ] , t − i + 1 ) \sum_{i=1}^{t}\sum_{j=t+1}^{n}min(F[i][j], t-i+1) i=1tj=t+1nmin(F[i][j],ti+1)​​:

  • 暴力的复杂度是 O ( n 3 ) O(n^3) O(n3)​。

如何缩短时间的复杂度?

t ′ = 5 t'=5 t=5​​​​​​时需要计算 ∑ i = 1 5 m i n ( F [ i ] [ 6 ] , t ′ − i + 1 ) \sum_{i=1}^{5}min(F[i][6],t'-i+1) i=15min(F[i][6],ti+1)​​​​​​,在 t = 4 t=4 t=4​​​​​​时需要计算 ∑ i = 1 4 m i n ( F [ i ] [ 5 ] , t − i + 1 ) + ∑ i = 1 4 m i n ( F [ i ] [ 6 ] , t − i + 1 ) \sum_{i=1}^{4}min(F[i][5],t-i+1)+\sum_{i=1}^{4}min(F[i][6],t-i+1) i=14min(F[i][5],ti+1)+i=14min(F[i][6],ti+1)​​​​​​。

可以看出,如果暴力计算的话,会重复计算 ∑ i = 1 4 F [ i ] [ 6 ] \sum_{i=1}^{4}F[i][6] i=14F[i][6]​​。

考虑如何将 t ′ = 5 t'=5 t=5​​​的状态推到 t = 4 t=4 t=4​​​的状态?

因为t的值不相同,所以无法直接记录 ∑ m i n ( F [ i ] [ j ] , t ′ − i + 1 ) \sum min(F[i][j],t'-i+1) min(F[i][j],ti+1)​​​​的值。

可以将 F [ i ] [ j ] F[i][j] F[i][j]​​的值记录在数组 c n t [ i ] [ F [ i ] [ j ] ] cnt[i][F[i][j]] cnt[i][F[i][j]]​​中,又因为每次枚举t都会向前移动一位( t ′ − t = 1 t'-t=1 tt=1​​),

所以只需要​考虑等于 ( t ′ − i + 1 ) = ( t − i + 1 + 1 ) (t'-i+1)=(t-i+1+1) (ti+1)=(ti+1+1)​​​的值,这个值超过了分割线t

ans -= cnt[i][t - i + 1 + 1];
cnt[i][t - i + 1] += cnt[i][t - i + 1 + 1];

然后计算 ∑ i = 1 t m i n ( F [ i ] [ t ′ ] , t − i + 1 ) \sum_{i=1}^{t}min(F[i][t'],t-i+1) i=1tmin(F[i][t],ti+1)​​​的值。

int j = t + 1;// B的子串的起始位置 
for(int i = 1; i <= t; i++){
    int len = min(F[i][j], t - i + 1);// 不能超过分割线 
    cnt[i][len]++;
    ans += len;
}

还需要去掉左边以 t ′ t' t​开头的子串​​的贡献。

ans -= cnt[t + 1][1] * 1;

AC的代码

#include<bits/stdc++.h>
using namespace std;
const int N = 3000+10;
int n, k, F[N][N], res[N], cnt[N][N];
char s[N];

void init(){
	memset(cnt, 0, sizeof cnt);
	memset(F, 0, sizeof F);
	memset(res, 0, sizeof res);
}

int main(){
	int T;
	cin>>T;
	 
	while(T--){
		init();// 初始化 
		cin>>n>>k;
		cin>>s+1;
		// =============================================================
		// 预处理F[i][j] 
		for(int t = 1; t < n; t++){	// 枚举两个子串的左端点之差 
			int dif = 0;// 记录两个子串的不同
			for(int L = 1, R = L + t, len = 0; R <= n; L++, R++){
				// len表示对于此时的(i,j)满足k-match最长的长度 
				while(R + len <= n && dif <= k){
					if(s[L + len] != s[R + len]){
						dif++;
					}
					len++;
				}
				if(dif <= k){
					F[L][R] = len;
				}
				else{ //多匹配了一位 
					F[L][R] = len - 1;
				}
				
				// 将i和j都向右移动一位
				if(s[L] != s[R]) {
					dif--;
				}
				len--;
			}
		}
		// =============================================================
		int ans = 0;
		// 枚举A和B的分割位置 
		for(int t = n - 1; t >= 1; t--){
			for(int i = 1; i <= t; i++) {
				ans -= cnt[i][t - i + 1 + 1];
				cnt[i][t - i + 1] += cnt[i][t - i + 1 + 1];
			}
			
			int j = t + 1;// B的子串的起始位置 
			for(int i = 1; i <= t; i++){
				int len = min(F[i][j], t - i + 1);// 不能超过分割线
				cnt[i][len]++;
				ans += len;
			}
			
			ans -= cnt[t + 1][1] * 1;
			
			res[t] = ans;
		}
		// =============================================================
		for(int i = 1; i < n; i++) {
			cout<<res[i]<<endl;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值