BZOJ3145 Str 和 test20200421 字符串
BZOJ3145 Str
Arcueid,白姬,真祖的公主。在和推倒贵看电影时突然对一个问题产生了兴趣:
我们都知道真祖和死徒是有类似的地方。那么从现代科学的角度如何解释呢?自然就得研究遗传密码了。Arcueid得知了两者的DNA片段,想寻求一个DNA片段,使得其在两者的DNA中都出现过。
我们知道公主的脑袋有点不太灵活,如果两个DNA片段只有一个位置不同,她也会将其认为是相同的。所以请您找出这样的最长的DNA片段吧。
100% 的数据 n<=105;m<=105。
每个串中只包含小写字母(别问我为什么不是 ATCG )。
题解
参照张天扬《后缀自动机及其应用》。
实现时我用后缀树代替了后缀数组(因为写不来),后缀树上的lcp就是lca的len了。然后启发式合并用了指针,因为涉及set操作所以代码显得有点乱
typedef pair<int,int> pii;
co int N=4e5+10;
char s[N];
int n,m;
namespace NS{ // negative SAM
int last=1,tot=1;
int ch[N][27],fa[N],len[N],pos[N],ref[N]; // ref:out->in
void extend(int c,int po){
int p=last,cur=last=++tot;
len[cur]=len[p]+1,pos[cur]=po,ref[po]=cur;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
if(!p) fa[cur]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[cur]=q; // edit 2: cur
else{
int clone=++tot;
memcpy(ch[clone],ch[q],sizeof ch[q]);
fa[clone]=fa[q],len[clone]=len[p]+1,pos[clone]=pos[cur]; // pos[clone] can be either pos[cur] or pos[q].
fa[cur]=fa[q]=clone;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
}
int son[N][27],rnk[N],dfn,st[N*2][21],lg[N*2];
void dfs(int p){
st[rnk[p]=++dfn][0]=len[p];
for(int c=0;c<27;++c)if(son[p][c]) // edit 1: son
dfs(son[p][c]),st[++dfn][0]=len[p];
}
void work(){
for(int i=n+m+1;i;--i) extend(s[i],i);
for(int i=1;i<=tot;++i) son[fa[i]][s[pos[i]+len[fa[i]]]]=i;
dfs(1);
for(int j=1;j<=20;++j)
for(int i=1,t=dfn-(1<<j)+1;i<=t;++i)
st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]); // edit 4: j-1
lg[0]=-1;for(int i=1;i<=dfn;++i) lg[i]=lg[i>>1]+1;
}
int lcp(int a,int b){ // a,b is rnk
if(!a||!b) return 0;
if(a>b) swap(a,b);
int k=lg[b-a+1];
return min(st[a][k],st[b-(1<<k)+1][k]);
}
}
namespace PS{ // positive SAM
int last=1,tot=1;
int ch[N][27],fa[N],len[N];
set<pii>*rht[N]={new set<pii>,new set<pii>};
typedef set<pii>::iterator it;
void extend(int c,int po){
int p=last,cur=last=++tot;
len[cur]=len[p]+1,rht[cur]=new set<pii>,rht[cur]->insert(pii(NS::rnk[NS::ref[po+2]],po));
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
if(!p) fa[cur]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[cur]=q; // edit 2: cur
else{
int clone=++tot;
memcpy(ch[clone],ch[q],sizeof ch[q]);
fa[clone]=fa[q],len[clone]=len[p]+1,rht[clone]=new set<pii>;
fa[cur]=fa[q]=clone;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
}
int cnt[N],ord[N];
int ans;
bool valid(int x) {return x<=n?x<n:x<n+m+1;}
void work(){
for(int i=1;i<=n+m+1;++i) extend(s[i],i);
for(int i=1;i<=tot;++i) ++cnt[len[i]];
for(int i=1;i<=n+m+1;++i) cnt[i]+=cnt[i-1]; // edit 3: cnt
for(int i=1;i<=tot;++i)
ord[cnt[len[i]]--]=i,rht[i]->insert(pii(-1,0)),rht[i]->insert(pii(NS::dfn+1,0));
for(int i=tot;i;--i){
int p=ord[i];
if(rht[fa[p]]->size()<rht[p]->size()) swap(rht[fa[p]],rht[p]);
for(it j=rht[p]->begin(),k;j!=rht[p]->end();++j)if(j->second){
rht[fa[p]]->insert(*j),k=rht[fa[p]]->find(*j),--k;
if(k->second&&j->second<=n^k->second<=n){
if(valid(j->second)&&valid(k->second))
ans=max(ans,len[fa[p]]+1+NS::lcp(j->first,k->first));
else ans=max(ans,len[fa[p]]);
}
k=rht[fa[p]]->find(*j),++k;
if(k->second&&j->second<=n^k->second<=n){
if(valid(j->second)&&valid(k->second))
ans=max(ans,len[fa[p]]+1+NS::lcp(j->first,k->first));
else ans=max(ans,len[fa[p]]);
}
}
rht[p]->clear();
}
printf("%d\n",ans);
}
}
int main(){
scanf("%s",s+1),n=strlen(s+1),s[n+1]='z'+1;
scanf("%s",s+n+2),m=strlen(s+n+2);
for(int i=1;i<=n+m+1;++i) s[i]-='a';
NS::work(),PS::work();
return 0;
}
test20200421 字符串
你喜欢字符串。有人送了你一个仅含小写字母的字符串。
由于你是一名优秀的 𝑂𝐼𝑒𝑟,所以你决定对这个字符串展开研究。
定义两个字符串是相似的,当且仅当存在至多一个 𝑖 ,使得这两个字符串中只有第 𝑖 个字母不同。
你取出了这个字符串中所有长度为 𝑚 的子串。你想知道,对于每个长度为 𝑚 的子串,有多少个其它长度为 𝑚 的子串与它相似。
对于 100% 的数据,1 ≤ 𝑛 ≤ 105,1 ≤ 𝑚 ≤ n。
题解
lh《FJWC Day3》。
𝑆, 𝑇 相似,等价于 𝐿𝐶𝑃(𝑆, 𝑇) + 𝐿𝐶𝑆(𝑆, 𝑇) ≥ 𝑚 − 1。
考虑建出原串的前缀树和后缀树,每个子串都能分别对应前后缀树上的一个点,两个串的 LCP 就是这两个串所对应的点在前缀树上的 LCA 的深度,LCS 同理。
那么就是对每个点 𝑢,求有多少个点 𝑣 满足 𝑑𝑒𝑝1[𝑙𝑐𝑎1(𝑢, 𝑣)] + 𝑑𝑒𝑝2[𝑙𝑐𝑎2(𝑢, 𝑣)] ≥ 𝑚 − 1。
在第一棵树上做 dsu on tree,加入一个点 𝑢 时,可以知道 𝑑𝑒𝑝1[𝑙𝑐𝑎1(𝑢, 𝑣)] ,那么只要求有多少个点满足它与 𝑢 在第二棵树上的 LCA 的深度 ≥ 𝑚 − 1 − 𝑑𝑒𝑝1[𝑙𝑐𝑎1(𝑢, 𝑣)] ,就是求 𝑢 往根路径上的深度最小且满足上述要求的点的子树中,有多少个点在第一棵树中被 dsu 到。
考虑用树状数组维护每个点在第二棵树上的 dfs 序,那么它就是一个单点加区间查。
但是这样只能算出有多少对 𝑢, 𝑣 满足 𝑑𝑒𝑝1[𝑙𝑐𝑎1(𝑢, 𝑣)] + 𝑑𝑒𝑝2[𝑙𝑐𝑎2(𝑢, 𝑣)] ≥ 𝑚 − 1。
再用一棵树状数组维护当前被 dsu 到的节点的答案,跟上一棵树状数组相反,这个要支持区间加,单点查。
这样就成功解决了本题。
复杂度 𝑂(𝑛 log2 𝑛)。
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#define MN 201000
int h[MN], nxt[MN], to[MN], K = 0;
int fa[MN], ch[MN][26], Max[MN], tot = 0, Max2[MN];
int dfn[MN], siz[MN], siz2[MN], st[MN][18], D = 0, son[MN];
int w1[MN], w2[MN];
int c[MN], c2[MN], Ans[MN];
int n, m;
char s[MN];
std::vector<int> v[MN];
void add(int *c, int x, int v) {for(int i = x; i <= D; i += i & -i) c[i] += v;}
void modify(int l, int r, int v) {add(c, l, v); if(r < D) add(c, r + 1, -v);} // diff
int query(int *c, int x) {int ans = 0; for(int i = x; i; i -= i & -i) ans += c[i]; return ans;}
void ins(int u, int v) {nxt[++K] = h[u]; h[u] = K; to[K] = v;}
void extend(int &lst, char s, int &p)
{
int u = ++tot, v = lst;
while(v && !ch[v][s - 'a']) ch[v][s - 'a'] = u, v = fa[v];
int o = ch[v][s - 'a'];
if(!o) {fa[u] = 1;}
else if(Max[o] == Max[v] + 1) fa[u] = o;
else
{
int n = ++tot; Max[n] = Max[v] + 1;
while(ch[v][s - 'a'] == o) ch[v][s - 'a'] = n, v = fa[v];
fa[u] = n; fa[n] = fa[o]; fa[o] = n;
memcpy(ch[n], ch[o], sizeof(ch[o]));
}
Max[u] = Max[lst] + 1;
lst = u; p = u;
}
void dfs1(int x, int fa) // dfn,st
{
dfn[x] = ++D; siz[x] = 1;
st[x][0] = fa;
for(int i = 1; i <= 17; i++) st[x][i] = st[st[x][i - 1]][i - 1];
for(int i = h[x]; i; i = nxt[i])
{
dfs1(to[i], x);
siz[x] += siz[to[i]];
}
}
int kth(int x, int k)
{
for(int i = 17; i >= 0; i--)
if(Max2[st[x][i]] >= k) x = st[x][i];
return x;
}
void dfs2(int x) // siz2
{
siz2[x] = 1;
for(int i = h[x]; i; i = nxt[i])
{
dfs2(to[i]);
siz2[x] += siz2[to[i]];
if(siz2[to[i]] > siz2[son[x]]) son[x] = to[i];
}
}
void calc1(int x) // modify
{
for(int i = 0; i < v[x].size(); i++)
{
int X = w1[v[x][i]];
modify(dfn[X], dfn[X], -query(c, dfn[X]));
add(c2, dfn[X], 1);
}
for(int i = h[x]; i; i = nxt[i]) calc1(to[i]);
}
void calc2(int x, int k) // query
{
for(int i = 0; i < v[x].size(); i++)
{
int y = kth(w1[v[x][i]], k);
modify(dfn[y], dfn[y] + siz[y] - 1, 1);
Ans[v[x][i]] += query(c2, dfn[y] + siz[y] - 1) - query(c2, dfn[y] - 1);
}
for(int i = h[x]; i; i = nxt[i]) calc2(to[i], k);
}
void dfs4(int x) // clear
{
for(int i = 0; i < v[x].size(); i++)
{
Ans[v[x][i]] += query(c, dfn[w1[v[x][i]]]);
add(c2, dfn[w1[v[x][i]]], -1);
}
for(int i = h[x]; i; i = nxt[i]) dfs4(to[i]);
}
void dfs3(int x, bool ok) // dsu on tree
{
for(int i = h[x]; i; i = nxt[i])
if(to[i] != son[x])
dfs3(to[i], 0);
if(son[x]) dfs3(son[x], 1);
for(int i = 0; i < v[x].size(); i++)
{
int y = kth(w1[v[x][i]], m - 1 - Max[x]);
modify(dfn[y], dfn[y] + siz[y] - 1, 1);
Ans[v[x][i]] += query(c2, dfn[y] + siz[y] - 1) - query(c2, dfn[y] - 1);
}
for(int i = 0; i < v[x].size(); i++)
{
int X = w1[v[x][i]];
modify(dfn[X], dfn[X], -query(c, dfn[X])); // initialize to 0
add(c2, dfn[X], 1);
}
for(int i = h[x]; i; i = nxt[i])
{
if(to[i] != son[x])
{
calc2(to[i], m - 1 - Max[x]);
calc1(to[i]);
}
}
if(!ok) dfs4(x);
}
int main()
{
// freopen("string.in", "r", stdin);
// freopen("string.out", "w", stdout);
Max[0] = -1e9;
scanf("%d%d%s", &n, &m, s + 1);
int lst = 1; tot = 1;
for(int i = 1; i <= n; i++) extend(lst, s[i], w1[i]); // w1: idx
memcpy(Max2, Max, sizeof(Max));
for(int i = 2; i <= tot; i++) ins(fa[i], i);
dfs1(1, 0); // dfn,st
memset(h, 0, sizeof(h)); K = 0;
lst = 1; tot = 1; memset(ch, 0, sizeof(ch));
for(int i = n; i >= 1; i--)
{
extend(lst, s[i], w2[i]);
if(i + m - 1 <= n) v[w2[i]].push_back(i + m - 1);
}
for(int i = 2; i <= tot; i++) ins(fa[i], i);
dfs2(1); dfs3(1, 0);
for(int i = m; i <= n; i++) printf("%d ", Ans[i]);
}