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]);
}

posted on 2020-04-21 16:39  autoint  阅读(156)  评论(0编辑  收藏  举报

导航