题解:P12923 [POI 2021/2022 R3] 模板 2 / Szablon 2
jiezecheng · · 题解
令 Bajtazar 想要的文字为
如果我们要枚举合法的模版,这个模版一定要包含
暴力
假设我们现在枚举了一个
将问题抽象为一个铺路问题。
- 初始时所有字符都是没有铺过的。
- 有两种瓷砖
s_{[1:l]} 与\overline{s_{[1:l]}} 。 - 我们每次都从路面最左端开始。设已经铺过的路为
s_{[1:p]} ,合法的起点为[1,p+1] ,接下来要选一种瓷砖,在某个合法起点上铺下,使新覆盖的区域尽量向右延伸,并更新p 。 - 为了将
s 铺完,我们每次都选最靠右的能铺上瓷砖的下标。
以样例
此时枚举
此时
- 在
[1,1] 选择下标1 ,因为s_{[1 :l]}=t ,此时将p 铺到了5 。
- 在
[1,6] 选择下标4 ,因为s_{[4 :4+l-1]}=s_{[4,8]}=t ,此时将p 铺到了8 。
- 在
[1,9] 选择下标8 ,因为s_{[8 :8+l-1]}=s_{[8,12]}=\overline{t} ,此时将p 铺到了12 。
- 在
[1,13] 选择下标12 ,因为s_{[12 :12+l-1]}=s_{[12,16]}=t ,此时将p 铺到了16 。
由于最后
那么如果我们如此暴力匹配,时间复杂度是多少呢?
当
如果你要更新红蓝黄三种匹配,倒不如只更新红黄两种颜色,也就是说,查询
可以发现,两次更新一定会至少更新
顺便说一下,如果没有
优化
如何优化?
瓶颈有二。
1. 子串与前缀的匹配。
我们可以用 Z 函数来直接查询任意下标
同理,也可以用 Z 函数查询任意下标
2. 如何查询 [1,p+1] 中可以匹配 t 或 \overline{t} 的最右下标。
我们考虑到,到底一个点为什么不能作为匹配起点。
-
初始时所有位置都可用(都可以作为起点)。
-
随着
l 增大,某些位置的z 值不够用了(l > z_i ),这些点,无法成为起点了,且由于我们的l 时从大到小枚举的,这些点就再也用不着了! -
查询时我们可以从
p+1 一路向左,找到第一个仍有效的起点。
所以我们可以利用并查集来维护这个信息。如果一个点
总时间复杂度
代码
还有一些小细节,如最开始
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int z[N];
void get_z(string s,int n){
for(int i=2;i<=n;i++) z[i]=0;
z[1]=n;
int l=1,r=1;
for(int i=2;i<=n;i++){
if(i<=r) z[i]=min(z[i-l+1],r-i+1);
while(i+z[i]<=n and s[z[i]+1]==s[i+z[i]]) z[i]++;
if(i+z[i]-1>r){
l=i;
r=i+z[i]-1;
}
}
}
int z_fr[N];
int z_re[N];
struct DSU{
int fa[N];
int n;
DSU(int sze){
n=sze;
for(int i=0;i<=n;i++) fa[i]=i;
for(int i=n+1;i<=n+n;i++) fa[i]=n;
}
int Find(int x){
if(fa[x]==x) return x;
fa[x]=Find(fa[x]);
return fa[x];
}
void del(int x){
fa[x]=Find(x-1);
}
void print(){
for(int i=1;i<=n;i++) cout<<Find(i)<<" ";
cout<<"\n";
}
};
vector<int> dele_fr[N];
vector<int> dele_re[N];
int main(){
string s;
cin>>s;
int n=s.size();
string rs=s;reverse(rs.begin(),rs.end());
get_z(" "+s,n);
for(int i=1;i<=n;i++) z_fr[i]=z[i];
get_z(" "+s+"#"+rs,n+n+1);
for(int i=n+2;i<=n+n+1;i++) z_re[i-n-1]=z[i];
reverse(z_re+1,z_re+n+1);
// for(int i=1;i<=n;i++) cout<<z_fr[i]<<" ";
// cout<<"\n";
// for(int i=1;i<=n;i++) cout<<z_re[i]<<" ";
// cout<<"\n";
DSU d_fr(n),d_re(n);
for(int i=1;i<=n;i++) dele_fr[z_fr[i]+1].push_back(i);
for(int i=1;i<=n;i++) dele_re[z_re[i]+1].push_back(i);
for(int len=1;len<=n;len++){
for(int i:dele_fr[len]) d_fr.del(i);
for(int i:dele_re[len]) d_re.del(i);
int now=0;
// d_fr.print();
// d_re.print();
// cout<<"len : "<<len<<"\n";
while(1){
// cout<<now<<"\n";
int fa_fr=d_fr.Find(now+1);
int fa_re=d_re.Find(now+len);
if(fa_fr+len-1<=now and fa_re<=now) break;
now=max(fa_fr+len-1,fa_re);
if(now>=n){
cout<<len<<" ";
break;
}
}
}
}