[HEOI2015]最短不公共子串

题意:

洛谷链接

给出两个字符串 \(A\)\(B\),分别求出:
1.最短的串,是 \(A\) 的子串,不是 \(B\) 的子串。
2.最短的串,是 \(A\) 的子串,不是 \(B\) 的子序列。
3.最短的串,是 \(A\) 的子序列,不是 \(B\) 的子串。
4.最短的串,是 \(A\) 的子序列,不是 \(B\) 的子序列。


一道四合一题,如果分开做前两个好说,后两个得有点操作 ,不过由于我太菜做不得。而且暂时还不想学后两个点单独怎么做。

那我们把这四个看成一类问题。

考虑自动机:一个节点表示一个状态,每条边表示状态的转移。由于子串和子序列都能建出自动机来,那么我们只需要对两个自动机进行相同的转移,则 \(A\) 中有而 \(B\) 中没有的状态就是我们要找的答案。

子序列建成自动机这个应该都会,由于希望最长的匹配,肯定相同的字符越靠前越好。每个位置是一个点,向后每个字符往离它最近的点连边,时间复杂度与空间复杂度均为 \(O(n*\) 字符集大小\()\)

子串建成自动机可以选择前缀自动机也可选择后缀自动机,而这题数据范围是 \(2000\),因为复杂度瓶颈并不在这里,当然如果不想考虑空间的话可以写 \(sam\)。我们这里只需要用 \(trie\) 当自动机就可以了,把每个子串取出来建立一个 \(trie\),字符总长度 \(O(n^2)\),时间复杂度与空间复杂度均为 \(O(n^2*字符集大小)\)。看着大算算其实可过。

那么自动机就建好了,每次询问是什么,就把两个字符串建成相应的自动机就好了。那么现在考虑如何找答案。

因为一条边相当于加进去一个字符,我们进行 \(BFS\),遇到第一个 \(A\) 中存在而 \(B\) 中不存在的状态就把长度 \(+1\) 输出出来。如果遍历 \(A\) 的完整个自动机(自动机肯定不会有环的),还没有找到一个 \(B\) 中没有的状态,那 \(A\) 就太逊了,输出 \(-1\)

考虑 \(BFS\) 复杂度:\(trie\) 进行 \(BFS\) 时,由于 \(trie\) 是像树一样的结构,每个点只会被遍历一次,复杂度不会超过 \(O(n^2)\) 。而当两个序列自动机进行遍历的时候,由于到达一个点可以通过不同的路径,如果无脑加点会被卡成指数级。我们需要用 \(vis\) 数组记录一下哪些状态到达过,由于序列自动机节点有 \(n\) 个,这样时间复杂度与空间复杂度也都是 \(O(n^2)\) 的,而且这个复杂度瓶颈不可避,前面自然也就不需要那么优了。

不过要注意只有两个自动机同时为序列自动机的时候才用 \(vis\),否则 \(trie\) 那么多点也开不下是吧~

#include <bits/stdc++.h>
using namespace std;
const int N=101010;
const int qwq=2000100;
const int inf=0x3f3f3f3f;

struct T {

	char s[N];
	int n;
	int ch[qwq][26],cnt,now,rt;

	void trie() {
		memset(ch,0,sizeof(ch));
		rt = now = cnt = 1;
		for(int i=1;i<=n;i++) {
			now = rt;
			for(int j=i;j<=n;j++) {
				int c = s[j] - 'a';
				if(!ch[now][c]) ch[now][c] = ++cnt;
				now = ch[now][c];
			}
		}
	}

	void lie() {
		memset(ch,0,sizeof(ch));
		cnt = n; rt = 0;
		for(int i=1;i<=n;i++) {
			for(int j=i-1;j>=0;j--) {
				ch[j][ s[i]-'a' ] = i;
				if(s[j]==s[i]) break;
			}
		}
	}

} A,B;

struct E{ int x,y,le; };
queue <E> q;
bool vis[2333][2333];

int query() {
	bool flag = 0;
	if(A.rt==0 && B.rt==0) flag = 1;
	q.push( (E){A.rt,B.rt,0} );
	vis[A.rt][B.rt] = 1;
	while(!q.empty()) {
		E now = q.front(); q.pop();
		for(int i=0;i<25;i++) {
			int u = A.ch[now.x][i], v = B.ch[now.y][i];
			if(flag) { if(vis[u][v]) continue; }
			if(!u) continue;
			if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; }
			q.push( (E){u,v,now.le+1} );
			if(flag) vis[u][v] = 1;
		}
	}
	return -1;
}

int main() {
	int ans4;
	scanf("%s%s",A.s+1,B.s+1);
	A.n = strlen(A.s+1); B.n = strlen(B.s+1);
	A.trie(); B.trie(); cout<<query()<<"\n";
	B.lie();            cout<<query()<<"\n";
	A.lie();            ans4 = query();
	B.trie();           cout<<query()<<"\n"<<ans4;
	return 0;
}

\(sam\) 的做法:

后缀自动机时间建立的复杂度是 \(O(n)\) 的,空间复杂度是 \(O(n*26)\) 的,这让人很舒服,但是由于到达一个点同样有多条路径,我们每次 \(BFS\) 都要记录 \(vis\)

要注意 \(sam\) 数组要开两倍。

#include <bits/stdc++.h>
using namespace std;
const int N=101010;
const int qwq=2000100;
const int inf=0x3f3f3f3f;

struct T {

	char s[2333];
	int n;
	int ch[4333][26],fa[4333],len[4333],cnt,now,rt;

	void insert(int c) {
		int u = now; now = ++cnt;
		len[cnt] = len[u] + 1;
		for(; u&&!ch[u][c]; u=fa[u]) ch[u][c] = cnt;
		if(!u) fa[cnt] = 1;
		else {
			int zjt = ch[u][c];
			if(len[zjt]==len[u]+1) fa[cnt] = zjt;
			else {
				int ob = cnt+1;
				memcpy(ch[ob],ch[zjt],sizeof(ch[zjt]));
				fa[ob] = fa[zjt]; len[ob] = len[u] + 1;
				fa[cnt] = fa[zjt] = ob;
				for(; u&&ch[u][c]==zjt; u=fa[u]) ch[u][c] = ob;
				cnt++;
			}
		}
	}

	void sam() {
		memset(ch,0,sizeof(ch));
		now = cnt = rt = 1;
		for(int i=1;i<=n;i++) insert(s[i]-'a');
	}

	void lie() {
		memset(ch,0,sizeof(ch));
		cnt = n; rt = 0;
		for(int i=1;i<=n;i++) {
			for(int j=i-1;j>=0;j--) {
				ch[j][ s[i]-'a' ] = i;
				if(s[j]==s[i]) break;
			}
		}
	}

} A,B;

struct E{ int x,y,le; };
queue <E> q;
bool vis[4333][4333];

int query() {
	memset(vis,0,sizeof(vis));
	q.push( (E){A.rt,B.rt,0} );
	vis[A.rt][B.rt] = 1;
	while(!q.empty()) {
		E now = q.front(); q.pop();
		for(int i=0;i<25;i++) {
			int u = A.ch[now.x][i], v = B.ch[now.y][i];
			if(vis[u][v] || !u) continue;
			if(u && !v) { while(!q.empty()) q.pop(); return now.le+1; }
			q.push( (E){u,v,now.le+1} );
			vis[u][v] = 1;
		}
	}
	return -1;
}

int main() {
	int ans4;
	scanf("%s%s",A.s+1,B.s+1);
	A.n = strlen(A.s+1); B.n = strlen(B.s+1);
	A.sam(); B.sam(); cout<<query()<<"\n";
	B.lie();            cout<<query()<<"\n";
	A.lie();            ans4 = query();
	B.sam();           cout<<query()<<"\n"<<ans4;
	return 0;
}
posted @ 2020-05-13 19:13  maple276  阅读(219)  评论(0编辑  收藏  举报