蓝书上两道后缀数组题(Uva11107和Uva12206)的后缀自动机解法

UVA - 11107 Life Forms
按字典序求出所有至少在一半字符串内出现的公共子串。
朴素想法是多串以未出现字符分割,然后前推right集合,用位运算来判断当前节点被几个串更新过(这里n最大有100,ull也存不下,所以用bitset)。
但是会t。猜测原因可能是自动机太大了,还要跑基数排序。
用广义sam就过了。
至于输出具体的子串,按字典序dfs即可。
ac代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 300005;
char s[maxn];

struct Sam {
	int next[maxn][27];
	int link[maxn], step[maxn];
	bitset<105> mark[maxn];
	int a[maxn], b[maxn];
	int sz, last, root, maxx;
	char ans[maxn];

	void init() {
		memset(next, 0, sizeof(next));
		memset(a, 0, sizeof(a));
		for(int i = 0; i <= sz; i++) {
			mark[i].reset();
		}
		sz = last = root = 1;
		maxx = -1;
	}

	void add(int c) {
		if(next[last][c] && step[last] + 1 == step[next[last][c]]) {
			last = next[last][c];
			return;
		}

		int p = last;
		int np = ++sz;
		last = np;

		step[np] = step[p] + 1;
		while(!next[p][c] && p) {
			next[p][c] = np;
			p = link[p];
		}

		if(p == 0) {
			link[np] = root;
		} else {
			int q = next[p][c];
			if(step[p] + 1 == step[q]) {
				link[np] = q;
			} else {
				int clone = ++sz;
				memcpy(next[clone], next[q], sizeof(next[q]));
				step[clone] = step[p] + 1;
				link[clone] = link[q];
				link[q] = link[np] = clone;
				while(next[p][c] == q && p) {
					next[p][c] = clone;
					p = link[p];
				}
			}
		}
	}

	void dfs(int x, int len, int n) {
		if(len > maxx || step[x] != len) {
			return;
		}
		if(step[x] == maxx && mark[x].count() > n) {
			ans[len] = '\0';
			printf("%s\n", ans);
			return;
		}
		for(int i = 0; i < 26; i++) {
			if(next[x][i]) {
				ans[len] = i + 'a';
				dfs(next[x][i], len + 1, n);
				ans[len] = 0;
			}
		}
	}

	void solve(int n) {
		init();
		int len, kind = -1;
		for(int i = 0; i < n; i++) {
			scanf("%s", s);
			len = strlen(s);
			last = 1;
			for(int j = 0; j < len; j++) {
				add(s[j] - 'a');
				mark[last][i] = 1;
			}
			add(26);
			kind = max(kind, len);
		}
		n >>= 1;
		for(int i = 1; i <= sz; i++) {
			a[step[i]]++;
		}
		for(int i = 1; i <= kind; i++) {
			a[i] += a[i - 1];
		}
		for(int i = 1; i <= sz; i++) {
			b[a[step[i]]--] = i;
		}
		for(int i = sz; i > 1; i--) {
			int e = b[i];
			mark[link[e]] |= mark[e];
			if(mark[e].count() > n) {
				// printf("%d\n",mark[e].count());
				maxx = max(maxx, step[e]);
			}
		}
		if(maxx == -1) {
			printf("?\n");
		} else {
			dfs(root, 0, n);
		}
	}
} sam;

int main() {
	int n;
	scanf("%d", &n);
	while(n) {
		sam.solve(n);
		scanf("%d", &n);
		if(n){
			printf("\n");
		}
	}
	return 0;
}

UVA - 12206 Stammering Aliens
求最长重复子串,同时输出他最后一次出现的起始下标。
维护一个start数组。
说是start,其实是end,是每个后缀结束的位置,再通过start[e] - step[e] + 1求出start。
注意考虑最大长度同时也要选取其中最靠右的。
比上一题简单。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 40005;
char s[maxn];

struct Sam {
	int next[maxn << 1][26];
	int link[maxn << 1], step[maxn << 1];
	int endpos[maxn << 1];
	int start[maxn << 1];
	int a[maxn], b[maxn << 1];
	int sz, last, len;

	void init() {
		//如多次建立自动机,加入memset操作
		memset(next, 0, sizeof(next));
		memset(a, 0, sizeof(a));
		memset(endpos, 0, sizeof(endpos));
		memset(start, 0, sizeof(start));
		sz = last = 1;
	}

	void add(int c, int m) {
		int p = last;
		int np = ++sz;
		last = np;

		endpos[np] = 1;
		start[np] = m;
		step[np] = step[p] + 1;
		while(!next[p][c] && p) {
			next[p][c] = np;
			p = link[p];
		}

		if(p == 0) {
			link[np] = 1;
		} else {
			int q = next[p][c];
			if(step[p] + 1 == step[q]) {
				link[np] = q;
			} else {
				int clone = ++sz;
				memcpy(next[clone], next[q], sizeof(next[q]));
				step[clone] = step[p] + 1;
				link[clone] = link[q];
				link[q] = link[np] = clone;
				while(next[p][c] == q && p) {
					next[p][c] = clone;
					p = link[p];
				}
			}
		}
	}

	void build() {
		init();
		len = strlen(s);
		for(int i = 0; i < len; i++) {
			add(s[i] - 'a', i);
		}
		for(int i = 1; i <= sz; i++) {
			a[step[i]]++;
		}
		for(int i = 1; i <= len; i++) {
			a[i] += a[i - 1];
		}
		for(int i = 1; i <= sz; i++) {
			b[a[step[i]]--] = i;
		}
	}

	void solve(int m) {
		build();
		int ans = -1, ans2 = -1;
		for(int i = sz; i > 1; i--) {
			int e = b[i];
			if(endpos[e] >= m) {
				// printf("%d %d %d\n", endpos[e], step[e], start[e] - step[e] + 1);
				if(step[e] >= ans) {
					ans = step[e];
					ans2 = max(ans2, start[e] - step[e] + 1);
				}
			}
			endpos[link[e]] += endpos[e];
			start[link[e]] = max(start[link[e]], start[e]);
		}
		if(~ans) {
			printf("%d %d\n", ans, ans2);
		} else {
			printf("none\n");
		}
	}
} sam;

/*
3
baaaababababbababbab
*/


int main() {
	int m;
	while(~scanf("%d", &m) && m) {
		scanf("%s", s);
		sam.solve(m);
	}
	return 0;
}

开了一套后缀数组的题,结果题题都会做,感觉都是sam的题,想了一下这样性价比太低了,跳坑去学树论。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值