字符串杂题


Little Elephant and Strings

来源:CF204E

做法一:$\mathrm{SA}+$ 主席树   

可以把所有串拼在一起,然后中间用特殊分隔符分开.   

考虑处理第 $\mathrm{i}$ 个串的 $\mathrm{l}$ 位置开始的子串. 

显然这个 $\mathrm{r}$ 具有单调性,所以可以二分最大的 $\mathrm{r}$. 

这个东西用双指针扫,然后每个区间用主席树查一下编号种类即可.   

时间复杂度为 $\mathrm{O(n \log n)}$.   

做法二: $\mathrm{SAM}$

构建广义后缀自动机,然后插入串的时候暴力更新 $\mathrm{pre}$ 的信息. 

最后查询的时候把串放到自动机上跑一遍倍增求一下即可. 

时间复杂度也是 $O(n \log n)$.    

p.s. 暴力跳的方法可能不太优美. 

这个问题等价于给定树上若干个点,求这些点所构成的树链的并.  

可以将所有点离线,按照 $\mathrm{dfs}$ 序排序,然后在相邻 $\mathrm{lca}$ 处减掉

做法三:$\mathrm{SA}+$单调队列.  

若固定左端点 $\mathrm{l}$, 则显然要找到最大的 $\mathrm{r}$ 使得种类不小于 $K$.  

所以可以用双指针维护数量恰好为 $\mathrm{K}$ 的区间,并维护这个区间的 $\mathrm{lcp}$.

对于一个位置 $\mathrm{p}$ 来说, $\mathrm{p}$ 的贡献就是包含 $\mathrm{p}$ 的 $\mathrm{lcp}$ 最大的.  

这个东西用线段树或单调队列维护一下即可.  

做法二:

#include 
#include 
#include 
#include 
#define N  200009 
#define ll long long 
#define pb push_back 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
char str[N], tp[N]; 
int n, K, st[N], ed[N], pre[N << 1], len[N << 1], ch[N << 1][26]; 
int last, tot, vis[N], cn[N], fa[N][19]; 
void extend(int c) {
	if(ch[last][c]) {
		int p = last, q = ch[last][c]; 
		if(len[q] == len[p] + 1) last = q; 
		else {
			int nq = ++ tot; 
			len[nq] = len[p] + 1; 
			vis[nq] = vis[q], cn[nq] = cn[q]; 
			memcpy(ch[nq], ch[q], sizeof(ch[q])); 
			pre[nq] = pre[q], pre[q] = nq; 
			for(; p && ch[p][c] == q; p = pre[p]) 
				ch[p][c] = nq; 
			last = nq; 
		}
	}
	else {
		int p = last, np = ++ tot; 
		len[np] = len[p] + 1, last = np; 
		for(; p && !ch[p][c]; p = pre[p]) 
			ch[p][c] = np; 
		if(!p) pre[np] = 1; 
		else {
			int q = ch[p][c]; 
			if(len[q] == len[p] + 1) pre[np] = q; 
			else {
				int nq = ++ tot; 
				len[nq] = len[p] + 1; 
				vis[nq] = vis[q], cn[nq] = cn[q]; 
				memcpy(ch[nq], ch[q], sizeof(ch[q])); 
				pre[nq] = pre[q], pre[np] = pre[q] = nq; 
				for(; p && ch[p][c] == q; p = pre[p]) 
					ch[p][c] = nq; 
			}
		}
	}
}
int jump(int x) {
	for(int i = 18; i >= 0; -- i) {
		if(cn[fa[x][i]] < K) x = fa[x][i]; 
	}
	return x;  
}
int main() {
	// setIO("input"); 
	scanf("%d%d", &n, &K); 
	last = tot = 1; 
	for(int i = 1; i <= n ; ++ i) {
		scanf("%s", tp + 1); 
		st[i] = ed[i - 1] + 1; 
		ed[i] = st[i] + strlen(tp + 1) - 1;
		last = 1;  
		for(int j = st[i]; j <= ed[i]; ++ j) {
			str[j] = tp[j - st[i] + 1]; 
			extend(str[j] - 'a');   
			int p = last; 
			while(p && vis[p] != i) {
				vis[p] = i, ++ cn[p], p = pre[p];  
			}
		}
	}   
	for(int i = 1; i <= tot; ++ i) fa[i][0] = pre[i]; 
	for(int i = 1; i < 19  ; ++ i) 
		for(int j = 1; j <= tot; ++ j) fa[j][i] = fa[fa[j][i - 1]][i - 1];    
	cn[0] = N; 
	for(int i = 1; i <= n ; ++ i) {
		int p = 1; 
		ll ans = 0ll; 
		for(int j = st[i]; j <= ed[i] ; ++ j) {
			int c = str[j] - 'a';  
			p = ch[p][c];  
			int nex = jump(p); 
			if(cn[nex] >= K) ans += len[nex];   
			else {
				nex = pre[nex]; 
				if(nex && cn[nex] >= K) ans += len[nex];  
			} 
		}
		printf("%I64d", ans); 
		if(i != n) printf(" "); 
	}
	return 0; 
}

Paper task

来源:CF653F

这道题的重点是模拟,后缀自动机只是帮助统计本质不同的工具.  

p.s. 后缀数组的做法类似,是利用 $\mathrm{height[x]}$ 数组来保持本质不同的条件.   

$\mathrm{parent}$ 树和 $\mathrm{height}$ 数组都是有助于思考的同类型工具. 

#include 
#include 
#include 
#include 
#include 
#include 
#define M  500002 
#define N  1000009 
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std; 
int n, tot, last;
int prefix[N];   
int ch[N][2], pre[N], len[N], pos[N], size[N]; 
setse[N];   
set::iterator it, it2;
char str[N];             
void extend(int c) {
    int p = last, np = ++ tot; 
    len[np] = len[p] + 1, last = np;     
    for(;p && !ch[p][c]; p = pre[p])
        ch[p][c] = np;   
    if(!p) pre[np] = 1;  
    else {
        int q = ch[p][c]; 
        if(len[q] == len[p] + 1) pre[np] = q;  
        else {
            int nq = ++ tot; 
            len[nq] = len[p] + 1; 
            pos[nq] = pos[q];  
            memcpy(ch[nq], ch[q], sizeof(ch[q]));  
            pre[nq] = pre[q], pre[np] = pre[q] = nq;  
            for(;p&&ch[p][c]==q;p=pre[p]) ch[p][c]=nq;  
        }
    }
}    
int main() {
    // setIO("input");      
    last = tot = 1;     
    scanf("%d", &n);  
    scanf("%s", str + 1); 
    for(int i = n; i >= 1; -- i) {
        if(str[i] == '(') extend(0); 
        else extend(1);   
        pos[last] = i; 
    }     
    se[0 + M].insert(0);  
    for(int i = 1; i <= n ; ++ i) {
        prefix[i] = prefix[i - 1]; 
        if(str[i] == '(') ++ prefix[i]; 
        else -- prefix[i];  
        size[i] = se[prefix[i] + M].size(); 
        se[prefix[i] + M].insert(i); 
    }
    ll ans = 0; 
    for(int i = 2; i <= tot; ++ i) {
        int l = pos[i], pr = n, le = len[pre[i]];             
        it = se[prefix[l - 1] - 1 + M].lower_bound(l);  
        if(it != se[prefix[l - 1] - 1 + M].end()) {
            pr = (*it) - 1;   
        }             
        if(l + le > pr) {
            continue;   
        }          
        int cur = prefix[l - 1];                
        int L = l + le, R = min(pr, l + len[i] - 1);
        it = se[cur + M].lower_bound(L);  
        it2 = se[cur + M].lower_bound(R + 1);    
        if(it != se[cur + M].end()) {
            -- it2;   
            ans += size[*it2] - size[*it] + 1;                
        }
    }
    printf("%lld", ans); 
    return 0; 
}