1 条题解
-
0
想了一通没想到就是最暴力的做法。
对于每个长度 ,我们显然使用 的长度为 的前缀及其反串向后铺。假设我们已经铺路一段前缀,我们找到前缀最后一个可以向后拓展的位置 ,然后不断拓展即可。
这样做复杂度比想象中的要好的多。考虑每次向后拓展的长度。若连续两次拓展长度 ,即像下图一样。

那不如直接用最后一条去拓展,并且是可行的。也就是说暴力拓展的次数是 的。
剩下的就是怎么找到最右边的拓展位置。正着和反着割跑一遍 exKMP,就可以找出每个位置开头的正串和反串与 的 lcp。
从小到大枚举 ,每次要删去一些正串与反串,总数量是 的,并进行 次对于前缀的查询,使用线段树固然可以做到 ,时间压力有点大。不如直接用并查集,删除 就将 接到 上即可,查询前缀 就是 。
由于不好按秩合并,复杂度没变,但常数要小很多。
#include <bits/stdc++.h> #define eb emplace_back using namespace std; const int N=1e6+5; int n,z[N],rz[N]; vector<int> d[N],rd[N]; char s[N],t[N]; struct UU{ int n,fa[N]; void init(int N){n=N; for(int i=0;i<=n;++i)fa[i]=i; }int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }void del(int x){ fa[x]=find(x-1); }int ask(int x){return find(min(x,n));} }f,rf; signed main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin>>s+1,n=strlen(s+1); for(int i=1;i<=n;++i) t[i]=s[n-i+1]; z[1]=n; for(int i=2,l=0,r=0;i<=n;++i){ if(i<=r)z[i]=min(z[i-l+1],r-i+1); while(i+z[i]<=n&&s[i+z[i]]==s[z[i]+1])++z[i]; if(i+z[i]-1>r)l=i,r=i+z[i]-1; }for(int i=1,l=0,r=0;i<=n;++i){ if(i<=r)rz[i]=min(z[i-l+1],r-i+1); while(i+rz[i]<=n&&t[i+rz[i]]==s[rz[i]+1])++rz[i]; if(i+rz[i]-1>r)l=i,r=i+rz[i]-1; }reverse(rz+1,rz+n+1); // for(int i=1;i<=n;++i) // cout<<z[i]<<' '; // cout<<'\n'; // for(int i=1;i<=n;++i) // cout<<rz[i]<<' '; // cout<<'\n'; f.init(n),rf.init(n); for(int i=1;i<=n;++i) d[z[i]+1].eb(i),rd[rz[i]+1].eb(i); for(int i=1;i<=n;++i){ for(int v:d[i])f.del(v); for(int v:rd[i])rf.del(v); int p=0; while(1){ int q=max(f.ask(p+1)+i-1,rf.ask(p+i)); if(q<=p)break; if(q>=n){cout<<i<<' ';break;} p=q; } }cout<<'\n'; return 0; }
- 1
信息
- ID
- 3435
- 时间
- 12000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者