SPOJ - LCS2 Longest Common Substring II 后缀自动机 多个串的公共子串

题目链接:https://cn.vjudge.net/problem/SPOJ-LCS2

题解:求两个串的:https://blog.csdn.net/mmk27_word/article/details/98614115,dp[i]表示到 i 这个节点,匹配最长的公共长度,两个串的时候因为只匹配一次,所以我们取个最大值即可,如果多个的话,因为你不知道后面匹配的是哪个节点位置,所以要把每个节点的fa也要更新一下,每个节点取个最小值,最后取个最大值即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 250010;
int X[N << 1], Y[N << 1];
int cntans[N << 1], dp[N << 1];
struct SAM {
	int n; // 字符串长度 
	int tot; // 节点编号 
	int len[N << 1]; // 最长子串的长度 (该节点子串数量 = len[x] - len[fa[x]])
	int fa[N << 1]; // // 后缀链接 (最短串前部少一个字符所到达的状态) 
	int cnt[N << 1]; // 被后缀链接的个数,方便求节点字符串的个数
	int nex[N << 1][26]; // 下一个状态
	int last; // 最后一个节点
	ll num[N << 1]; // 该状态子串的数量
	ll maxx[N << 1]; // 长度为x的子串出现次数最多的子串的数目 
	ll sum[N << 1]; // 该节点后面所形成的自字符串的总数 
	ll ans; // 不同字符串数目 
	ll sublen; // 不同字符串总长度 
	void init(int x) {
		for(int i = 0; i <= x * 2 + 10; i++) {
			maxx[i] = 0;
			cnt[i] = 0;
			num[i] = 0;
			for(int j = 0; j <= 25; j++)
				nex[i][j] = 0;
		}
		n = 0; 
		last = 1;
		tot = 1; // 1是起始点 空集 
		fa[1] = 0;
		len[1] = 0;
		
	} 
	void insert (int c) { 
		n++;
		int x = ++tot; // 新建节点 
		len[x] = len[last] + 1;
		num[x] = 1;
		int p;
		for(p = last; p && !nex[p][c]; p = fa[p]) nex[p][c] = x; //沿着后缀连接 将所有没有字符c转移的节点直接指向新节点
		if(!p) fa[x] = 1, cnt[1]++; // 没有c的转移,直接链接到起点 
		else {
			int q = nex[p][c];
			if(len[p] + 1 == len[q]) // p q连续 
				fa[x] = q, cnt[q]++;
			else {
				int nq = ++tot; // 不连续 复制一份q的 使得和p连续 
				len[nq] = len[p] + 1;
				fa[nq] = fa[q];
				memcpy(nex[nq], nex[q], sizeof(nex[q]));
				for(; p && nex[p][c] == q; p = fa[p]) nex[p][c] = nq; //沿着后缀连接 将所有通过c转移为q的改为nq
				fa[q] = fa[x] = nq;
				cnt[nq] += 2;
			}
		}
		last = x;
	}
	void getposnum() { // 每个节点子串出现的次数 
		queue<int> q;
		int x;
		for(int i = 1; i <= tot; i++)
			if(!cnt[i])
				q.push(i);
		while(!q.empty()) {
			x = q.front(); q.pop();
			num[fa[x]] += num[x];
			if(--cnt[fa[x]] == 0) q.push(fa[x]);
		}
	}
	void getSUM() {
		for(int i = tot; i >= 1; i--) {
			sum[i] = num[i];
			for(int j = 0; j <= 25; j++)
				sum[i] += sum[nex[i][j]];
		//	cout << i << " " << sum[i] << endl;
		}
	}
	void getsubnum() { // 不同字符串数目 
		ans = 0;
		for(int i = 1; i <= tot; i++)
			ans += len[i] - len[fa[i]]; 
	} 
	void getmaxx() { // 长度为x的子串出现次数最多的子串的数目 
		// getposnum();
		for(int i = 1; i <= tot; i++) maxx[len[i]] = max(maxx[len[i]], num[i]);
		for(int i = n - 1; i >= 1; i--) maxx[i] = max(maxx[i], maxx[i + 1]);
	} 
	/*这也是计算节点子串出现次数的方法 
	void count() {
        memset(X,0,sizeof(X));
        for(int i=1;i<=tot;i++) X[len[i]]++;
        for(int i=1;i<=tot;i++) X[i]+=X[i-1];
        for(int i=1;i<=tot;i++) Y[X[len[i]]--]=i;
        for(int i=tot;i>=1;i--) num[fa[Y[i]]]+=num[Y[i]];
        
        for(int i = tot;i >= 1; i--) {
            sum[Y[i]] = 1;
            for(int j = 0 ;j < 25; j++)
                sum[Y[i]] += ans[nex[Y[i]][j]];
        }
        
	}
	*/
    void getsublen() {
    	sublen = 0;
    	for(int i = 1; i <= tot; i++) {
    		sublen += 1LL * (len[i] + len[fa[i]] + 1) * (len[i] - len[fa[i]]) / 2; 
		}
		//	cout << sublen << endl;
	}
	int compare(char str[]) {
		int l = strlen(str);
		int res = 0, cnt = 0;
		for(int i = 0, p = 1; i < l; i++) {
			if(nex[p][str[i] - 'a']) cnt++, p = nex[p][str[i] - 'a'];
			else {
				while(p && !nex[p][str[i] - 'a']) p = fa[p];
				if(!p) cnt = 0, p = 1;
				else cnt = len[p] + 1, p = nex[p][str[i] - 'a'];
			}
			dp[p] = max(dp[p], cnt);
		}
		for(int i = tot; i >= 2; i--)
			dp[fa[i]] = max(dp[fa[i]], min(dp[i], len[fa[i]]));
		for(int i = 2; i <= tot; i++) {
			if(cntans[i] == -1 || dp[i] < cntans[i])
				cntans[i] = dp[i];
			dp[i] = 0;
		}
			
	//	printf("%d\n", res);
		return res;
	}
}sam;
char s[N], t[N];
int k;
int main() {
	memset(cntans, -1, sizeof(cntans));
	int T, len;
	ll ans;
	scanf("%s", s + 1);
	len = strlen(s + 1);
	sam.init(len);
	for(int i = 1; i <= len; i++)
		sam.insert(s[i] - 'a');
	while(~scanf("%s", t + 1)) {
		sam.compare(t + 1);
	}
	int res = 0;
	for(int i = 2; i <= sam.tot; i++)
		res = max(res, cntans[i]); 
	printf("%d\n", res);
	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值