• 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
    */
    
  • 相关阅读:
    nginx的linux服务器内核参数调整【转】
    从运维角度来分析mysql数据库优化的一些关键点【转】
    10 个 MySQL 经典错误【转】
    keepalived的vip无法ping通【原创】
    MySQL数据库的锁详解【转】
    CGI,FastCGI,PHP-CGI与PHP-FPM区别详解【转】
    vim块编辑删除、插入、替换【转】
    Keepalived两节点出现双VIP情况及解决方法【原创】
    通过全备+relaylog同步恢复被drop的库或表【转】
    MySQL伪master+Binlog+同步【转】
  • 原文地址:https://www.cnblogs.com/ltdjcoder/p/15346998.html
Copyright © 2020-2023  润新知