SAM入门三题:字符串水题, LCS, P5546 [POI2000]公共串

刚学完SAM(也就学了三个月还没切板子), 学习笔记什么的话估计得过段时间
那先来几篇题解吧


字符串水题

传送门

题意

给出一个数字串S, 询问若干数字串T,问T中有多少子串在S中出现过,且满足各位数字之和在区间\([L, R]\)之间
注意:位置不同的两个相同子串算作两个子串

题解

显然的一个思路, 对于T中的每一个位置x, 我们统计T中以x开头的子串有多少符合条件的。
先考虑数字和, 显然的一个思路我们做前缀和然后二分, 得到一个l, r, l表示以x开头的子串, 最少要以l结尾, 最多以r结尾
这时候我们只要知道l,r之间有多少在s中出现过, 显然, 假如\(T_{x....y}\)出现过, 那么\(T_{x...z}(z<y)\)也出现过
仔细思考, 我们只要求出T中以x开头, 最远能匹配到哪里即可

好那我们现在求一个数组f, \(f_i\)表示T中以i开头最远能匹配到的位置,在s中出现过
怎么求呢: 我们先求\(f_1\), 考虑直接一位一位往右在sam上匹配,匹配到一个位置后无法在匹配, 就直接跳到link处匹配, 并且把$f_{1...link-1}都设为这个最远的位置
如果你还没懂的话直接看代码理解也不错

当然这个做法是为在没写板子的时候独立想出来的, (虽然sam是扣的
所以我重点来写一下我对sam用法的理解

想到这个做法的关键思路,在于对endpos的理解, 后缀自动机之所以有如此优秀的复杂度, 是因为它将所有endpos相同的子串归于一个等价类, 这些在同一等价类中的子串, 出现位置完全相同,他们的很多性质都一样, 这是优化复杂度的关键所在,

在这道题中, 同一个等价类中的子串的f值显然相同, (读者自证)
所以我们才能够每次跳到他的link, 由于节点数是O(n)的, 复杂度也是O(n)的

我觉得sam的题都应该往这个方向去考虑
(虽然我刚学

实现

细节还是很多的, 比如当我们无法匹配的时候, 也就是p变成虚拟状态, 也就是连空串都无法匹配的时候, 就需要让x++,pos++, 直接匹配下一个位置, 同时要让p=1,回到空串的状态

如果你要问ddd是什么的话, 本来是end的............, 关键字真难受

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <string>
#define ll long long
using namespace std;

int read(){
	int num=0, flag=1; char c=getchar();
	while(!isdigit(c) && c!='-') c=getchar();
	if(c=='-') flag=-1, c=getchar();
	while(isdigit(c)) num=num*10+c-'0', c=getchar();
	return num*flag;
}

namespace sam{
	struct state {
	  int len, link;
	  std::map<char, int> next;
	};
	
	const int MAXLEN = 300000;
	state st[MAXLEN * 2];
	int sz, last;
	
	void init() {
	  st[0].len = 0;
	  st[0].link = -1;
	  sz++;
	  last = 0;
	}
	
	void extddd(char c) {
	  int cur = sz++;
	  st[cur].len = st[last].len + 1;
	  int p = last;
	  while (p != -1 && !st[p].next.count(c)) {
	    st[p].next[c] = cur;
	    p = st[p].link;
	  }
	  if (p == -1) {
	    st[cur].link = 0;
	  } else {
	    int q = st[p].next[c];
	    if (st[p].len + 1 == st[q].len) {
	      st[cur].link = q;
	    } else {
	      int clone = sz++;
	      st[clone].len = st[p].len + 1;
	      st[clone].next = st[q].next;
	      st[clone].link = st[q].link;
	      while (p != -1 && st[p].next[c] == q) {
	        st[p].next[c] = clone;
	        p = st[p].link;
	      }
	      st[q].link = st[cur].link = clone;
	    }
	  }
	  last = cur;
	}
	
}

const int N = 200005;			

string s, t;
int Q, L, R;
int n, m;
int ddd[N];

void build(){
	sam::init();
	for(int i=0; i<s.size(); i++){
		sam::extddd(s[i]);
	}
}

int sum[N];

ll ans;

void solve(){
	ans = 0;
	int now=0, pos=0, np=0;
	while(now < m){
		while(pos<m && sam::st[np].next.count(t[pos])) {
			np = sam::st[np].next[t[pos]];
			pos++;
		}
		ddd[now] = pos-1;
		np = sam::st[np].link;
		if(np != -1){
			int nex = pos - sam::st[np].len;
			for(int i=now+1; i<nex; i++) ddd[i] = ddd[now];
			now = nex;
		}else{
			np=0, now++, pos++;
		}
	}
	
	for(int i=1; i<=m; i++){
		sum[i] = sum[i-1] + t[i-1] - '0';
	}
	
	for(int i=1; i<=m; i++){
		int l = lower_bound(sum+1, sum+1+m, L+sum[i-1])-sum;
		int r = upper_bound(sum+1, sum+1+m, R+sum[i-1])-sum-1;
		if(l>r || l>m || r>m) continue;
		int ex = ddd[i-1]+1;
		ans += max(0, min(ex, r)-max(l, i)+1);
	}
	cout << ans << endl;
}

void scan(){
	cin >> s; n=s.size();
	build();
	Q = read();
	while(Q--){
		cin >> t >> L >> R; m=t.size();
		solve();
	}
}

signed main(){
//	freopen("6.in", "r", stdin);
//	freopen("6.txt", "w", stdout);
	scan();
	return 0;
} 

LCS & P5546 [POI2000]公共串

LCS

让我们先来考虑如何求两个串的最长公共子串, 这题由于科学原因无法提交, 所以没有题目和代码(你可以在spoj上找到)

好吧两个串的lcs不就是上面f数组的最大值吗.........emm
所以要多思考算法的本质


多个串的LCS

传送门

当然这道题的正解我还不会, 下次upd
这里讲讲我的做法:
很简单:每个串都建一个sam不就行了!!!
很暴力把, 但很实用

我们用第一个串跟后面所有串匹配, 给后面每个串都建一个sam
然后求f数组, 由于我们f数组的含义是, 从i开头最远能匹配的距离, 那么从i开头的的最长的lcs,
显然这个总的fi值就是第一个串和后面每个串匹配的fi最小值(读自证)
那么就求求出来

但是xjq大佬说如果子串数量变多(这道题只有10)就会炸
当然所有字符串的长度和不变
我们来分析一下复杂度
每次匹配的复杂度显然是第一个串的长度\(|S|\)
假设有m个串,共匹配m次, 每次要建一个自动机, 复杂度为被匹配串的长度\(|T|\)
总复杂度就是\(O(m|S| + \sum|T|)\)
由于\(\sum|T|\)\(O(n)\)的, 关键在于\(|S|\)
显然假如S长度为\(n/2\), 其他串的长度为1时, 复杂度最大为\(O(n^2)\)

所以我假了
不不不, 这里有一个很显然的优化啊啊啊, 由于S可以是任意字符串, 那么当s为长度最小的串时复杂度最优
这时候,当所有串长度都为\(\sqrt n\)时复杂度最大
复杂度为\(O(\sqrt n \sqrt n + n)\)\(O(n)\)

所以没有假, 这个算法优化后能过

实现

这题给出通过此题的代码, 未加优化, 刚学sam可能比较丑....抱歉

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;

string s[10];


const int N = 20045;
int n, f[N];

namespace sam{
	struct node{
		int len, link;
		int ch[26];
		void init(){
			memset(ch, 0, sizeof(ch));
		}
	}st[N*2];
	int sz, las=1;
	
	void init(){
		sz = 0;
		st[1].len=0;
		st[1].link=0;
		sz++, las=1;
		for(int i=0; i<N*2; i++)  st[i].init();
	}
	
	void extend(int c){
		int cur=++sz, p=las; st[cur].len=st[las].len+1;
		while(p && !st[p].ch[c]) st[p].ch[c]=cur, p=st[p].link;
		if(p){
			int nex = st[p].ch[c];
			if(st[p].len+1 == st[nex].len){
				st[cur].link = nex;
			}else{
				int clone = ++sz; st[clone].len=st[p].len+1, st[clone].link=st[nex].link;
				for(int i=0; i<26; i++) st[clone].ch[i] = st[nex].ch[i];
				st[nex].link = clone; st[cur].link = clone;
				while(st[p].ch[c]==nex) st[p].ch[c] = clone, p=st[p].link; 
			}
		}else{
			st[cur].link = 1;
		}
		las = cur;
	}
	
}

int ans = 0;	

int main(){
	cin >> n; for(int i=1; i<=n; i++) cin >> s[i];
	
	sam::init();
	for(int i=0; i<s[1].size(); i++) sam::extend(s[1][i]-'a');
	int x=0, p=1, pos=0;
	while(x<s[2].size()){
		while(sam::st[p].ch[s[2][pos]-'a']) p = sam::st[p].ch[s[2][pos]-'a'], pos++;
		if(p){
			p = sam::st[p].link;
			int nexpos = pos - sam::st[p].len;
			for(int i=x; i<nexpos; i++) f[i] = pos - i;
			x = nexpos;
		}else x++, pos++,p=1;
	}
	
	for(int j=3; j<=n; j++){
		sam::init();
		for(int i=0; i<s[j].size(); i++) sam::extend(s[j][i]-'a');
		x=0, p=1, pos=0;
		while(x<s[2].size()){
			while(sam::st[p].ch[s[2][pos]-'a']) p = sam::st[p].ch[s[2][pos]-'a'], pos++;
			if(p){
				p = sam::st[p].link;
				int nexpos = pos - sam::st[p].len;
				for(int i=x; i<nexpos; i++) f[i] = min(f[i], pos - i);
				x = nexpos;
			}else f[x]=0, x++, pos++, p=1;
		}
	}
	
	for(int i=0; i<s[2].size(); i++) ans = max(f[i], ans);
	cout << ans << endl;
	
	return 0;
}
/*
5
fassssdfadfs22
fdfsfasdssss
dddddddddfasdsdfa
ssdafffffffffdddfffffddfa
intintintintfintintfifaint

5
faddddddfffd
ddddddfaddddd
dddfaddd
dddddddddfadd
dddddddddddddddfa

2
abcd
ceddbc
*/
posted @ 2021-09-28 11:15  ltdJcoder  阅读(120)  评论(0编辑  收藏  举报