P7469 [NOI Online 2021 提高组] 积木小赛(民间数据)题解

37 篇文章 0 订阅

题目链接

我的做法竟然跟官方题解一样 /jk

赛时的沧桑路程: O ( n 4 ) → O ( n 3 ) → O ( n 2 log ⁡ ( n 2 ) ) \mathcal O(n^4) \rarr \mathcal O(n^3) \rarr \mathcal O(n^2 \log (n^2)) O(n4)O(n3)O(n2log(n2))

O ( n 4 ) \mathcal O(n^4) O(n4)
考虑每一个 T T T 的连续子串,用 O ( n 2 ) \mathcal O(n^2) O(n2) 的DP来计算它是否为 S S S 的子串。
我们记当前枚举到的 T T T 的连续子串为 f f f
f i , j = 0 / 1 f_{i,j}=0/1 fi,j=0/1 表示 f 1 … j f_{1 \dots j} f1j 是否是 S 1 … i S_{1 \dots i} S1i 的子串
转移方程: f i , j = max ⁡ k = 0 i − 1 f k , j − 1 f_{i,j}= \max\limits_{k=0}^{i-1}f_{k,j-1} fi,j=k=0maxi1fk,j1
这里的转移可以通过前缀和优化做到 O ( n 2 ) \mathcal O(n^2) O(n2) 的DP复杂度
最后统计答案的时候哈希去一下重就可以了

O ( n 3 ) \mathcal O(n^3) O(n3)

由于刚刚的枚举的大部分 f f f 都互相满足前缀包含关系,所以我们可以考虑只在 T T T 中枚举连续子串的左端点 l l l
f i , j = 0 / 1 f_{i,j}=0/1 fi,j=0/1 表示 T l … l + j − 1 T_{l \dots l+j-1} Tll+j1 是否为 S 1 … i S_{1 \dots i} S1i 的子串,转移方程同上。
统计答案时同样需要去重

O ( n 2 log ⁡ ( n 2 ) ) \mathcal O(n^2 \log (n^2)) O(n2log(n2))

前面,我们DP考虑的都是字符串的完美匹配。现在,我们将状态改为如下:
求一个尽可能大的 f i , j f_{i,j} fi,j,满足所有以 T j T_j Tj 为右端点,且长度不大于 f i , j f_{i,j} fi,j 的所有 T T T 的连续子串都是 S 1 … i S_{1\dots i} S1i 的子串。
转移方程: f i , j = max ⁡ k = 0 i − 1 f k , j − 1 + 1 f_{i,j}= \max\limits_{k=0}^{i-1}f_{k,j-1}+1 fi,j=k=0maxi1fk,j1+1,其中加上的 1 1 1 为子串 T [ j , j ] T[j,j] T[j,j] 的贡献。这里也可以用同上的方法做到 O ( n 2 ) \mathcal O(n^2) O(n2)

现在,我们可以根据 f f f 数组得到所有同时是 S S S 的子串也是 T T T 的连续子串的字符串,可知这些字符串的总数为不超过 n 2 n^2 n2。那么,我们只需要需要通过哈希求出不同的字符串个数就可以了。

由于不知道怎样 O ( n 2 ) \mathcal O(n^2) O(n2) 哈希加去重,又不敢用 unordered_map。所以赛时只好写了一个 O ( n 2 log ⁡ ( n 2 ) ) \mathcal O(n^2 \log (n^2)) O(n2log(n2))sort + unique,希望人没事 /kk

PS: 在洛谷上不吸氧只能有90 /dk

//stage three complete
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int Maxn=3010;
const int Maxm=9000010;
typedef unsigned long long ull;
const ull base=27;
char a[Maxn],b[Maxn];
int s[Maxn],f[Maxn][Maxn];
int p[Maxn];
ull c[Maxm];
int n;
inline int read()
{
	int s=0,w=1;
	char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
	return s*w;
}
int main()
{
	// freopen("in.txt","r",stdin);
	// freopen("block.in","r",stdin);
	// freopen("block.out","w",stdout);
	n=read();
	scanf("%s",a+1);
	scanf("%s",b+1);
	for(int j=1;j<=n;++j)
	{
		for(int i=1;i<=n;++i)
		if(a[i]==b[j])
		f[i][j]=s[i-1]+1;

		for(int i=1;i<=n;++i)
		{
			s[i]=max(s[i-1],f[i][j]);
			p[j]=max(p[j],f[i][j]);
		}
	}
	int cnt=0;
	for(int i=1;i<=n;++i)
	{
		ull tmp=0;
		for(int j=i;j>i-p[i];--j)
		{
			tmp=tmp*base+(b[j]-'a'+1);
			c[++cnt]=tmp;
		}
	}
	sort(c+1,c+1+cnt);
	cnt=unique(c+1,c+1+cnt)-(c+1);
	printf("%d\n",cnt);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值