zoukankan      html  css  js  c++  java
  • LOJ2720 「NOI2018」你的名字 【SAM,线段树合并】

    题目描述:给定串 (S)(Q) 次询问,每次询问串 (T) 和两个正整数 (l,r),求有多少个字符串是 (T) 的子串且不为 (S[l:r]) 的子串。

    数据范围:(|Sigma|=26)(|S|,|T|le 5 imes 10^5)(sum|T|le 10^6)。LOJ时限4s,洛谷时限5s。


    首先看 (l=1,r=|S|) 如何做。先对 (S)(T) 构建 SAM,然后我们可以算出 (l_i) 表示 (T[1,i]) 的后缀并 (S) 的子串的长度最大值。这个可以用类似 AC 自动机的匹配方法在 SAM 上做匹配。(i) 右移的时候当前节点没有对应出边的话就缩小长度,否则走出边。根据 two-pointer,这个的时间复杂度是 (O(|T|)) 的。

    那么答案就是 (sum_{i=2}^{cnt}max(0,len[i]-max(len[fa[i]],l[id[i]]))),其中 (cnt) 为 SAM 的节点个数,(len[i]) 为该节点对应子串的最大长度,(fa[i]) 是 Parent 树上 (i) 的父亲,(id[i]) 是节点 (i) 对应的 right 集合中的一个数。因为在节点 (i) 表示的子串中,长度为 ((max(len[fa[i]],l[id[i]]),len[i]]) 的子串对答案有贡献。

    ((l,r) eq (1,|S|)),则答案计算时是没有区别的(至与 (T)(l[i]) 有关),所以要改变计算 (l[i]) 的方法。注意此时有些出边是无效的了,因为该出边到达的节点表示的任意一个子串在 ([l,r]) 没有出现过,这时我们就需要使用线段树合并来维护 right 集合,若该出边到达的节点在 ([l+Len,r]) 上出现过((Len) 为当前计算的最大长度),则可以转移该出边并将 (Len) 加一。

    时间复杂度 (O((|S|+sum |T|)log|S|)),空间复杂度 (O((|S|+|T|)|Sigma|))。具体实现可以看代码~

    #include<bits/stdc++.h>
    #define Rint register int
    using namespace std;
    typedef long long LL;
    const int N = 1000003, M = N << 1;
    template<typename T>
    inline void read(T &x){
    	int ch = getchar(); x = 0;
    	for(;ch < '0' || ch > '9';ch = getchar());
    	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
    }
    char S[N], T[N];
    int n, m, L, R, Q, ls[N << 5], rs[N << 5], cnt;
    void insert(int &x, int L, int R, int pos){
    	if(!x) x = ++ cnt;
    	if(L == R) return;
    	int mid = L + R >> 1;
    	if(pos <= mid) insert(ls[x], L, mid, pos);
    	else insert(rs[x], mid + 1, R, pos);
    }
    int merge(int x, int y){
    	if(!x || !y) return x ^ y;
    	int o = ++ cnt;
    	ls[o] = merge(ls[x], ls[y]);
    	rs[o] = merge(rs[x], rs[y]);
    	return o;
    }
    bool query(int x, int L, int R, int l, int r){
    	if(!x) return 0;
    	if(l <= L && R <= r) return 1;
    	int mid = L + R >> 1;
    	return l <= mid && query(ls[x], L, mid, l, r) || mid < r && query(rs[x], mid + 1, R, l, r);
    }
    namespace SAM1 {
    	int ch[M][26], fa[M], len[M], rt[M], cnt = 1, las = 1, c[M], id[M];
    	bool has[M];
    	void insert(int c){
    		int p = las, np = ++ cnt; len[np] = len[p] + 1; has[np] = true; las = np;
    		for(;p && !ch[p][c];p = fa[p]) ch[p][c] = np;
    		if(!p) fa[np] = 1;
    		else {
    			int q = ch[p][c];
    			if(len[q] == len[p] + 1) fa[np] = q;
    			else {
    				int nq = ++ cnt; has[nq] = false; len[nq] = len[p] + 1;
    				memcpy(ch[nq], ch[q], sizeof ch[q]);
    				fa[nq] = fa[q]; fa[q] = fa[np] = nq; rt[nq] = rt[q];
    				for(;ch[p][c] == q;p = fa[p]) ch[p][c] = nq;
    			}
    		}
    	}
    	void build(){
    		for(Rint i = 1;i <= n;++ i) insert(S[i] - 'a');
    		for(Rint i = 1;i <= cnt;++ i) ++ c[len[i]];
    		for(Rint i = 1;i <= n;++ i) c[i] += c[i - 1];
    		for(Rint i = cnt;i > 1;-- i) id[c[len[i]] --] = i;
    		for(Rint i = cnt;i > 1;-- i){
    			int p = id[i];
    			if(has[p]) ::insert(rt[p], 1, n, len[p]);
    			rt[fa[p]] = merge(rt[fa[p]], rt[p]);
    		}
    	}
    }
    namespace SAM2 {
    	int ch[M][26], fa[M], len[M], id[M], l[M], cnt, las;
    	void clear(){
    		for(Rint i = 0;i <= cnt;++ i){
    			memset(ch[i], 0, sizeof ch[i]);
    			fa[i] = len[i] = id[i] = l[i] = 0;
    		}
    		cnt = las = 1;
    	}
    	void insert(int c){
    		int p = las, np = ++ cnt; id[np] = len[np] = len[p] + 1; las = np;
    		for(;p && !ch[p][c];p = fa[p]) ch[p][c] = np;
    		if(!p) fa[np] = 1;
    		else {
    			int q = ch[p][c];
    			if(len[q] == len[p] + 1) fa[np] = q;
    			else {
    				int nq = ++ cnt; len[nq] = len[p] + 1;
    				memcpy(ch[nq], ch[q], sizeof ch[q]); id[nq] = id[q];
    				fa[nq] = fa[q]; fa[q] = fa[np] = nq;
    				for(;ch[p][c] == q;p = fa[p]) ch[p][c] = nq;
    			}
    		}
    	}
    	void main(){
    		clear();
    		scanf("%s", T + 1); m = strlen(T + 1);
    		read(L); read(R);
    		int Len = 0, now = 1;
    		for(Rint i = 1;i <= m;++ i){
    			int c = T[i] - 'a';
    			insert(c);
    			while(true){
    				if(SAM1::ch[now][c] && query(SAM1::rt[SAM1::ch[now][c]], 1, n, L + Len, R)){
    					now = SAM1::ch[now][c]; ++ Len; break;
    				}
    				if(!Len) break; -- Len;
    				if(Len == SAM1::len[SAM1::fa[now]]) now = SAM1::fa[now];
    			}
    			l[i] = Len;
    		}
    		LL ans = 0;
    		for(Rint i = 2;i <= cnt;++ i) ans += max(0, len[i] - max(len[fa[i]], l[id[i]]));
    		printf("%lld
    ", ans);
    	}
    }
    int main(){
    	scanf("%s", S + 1); n = strlen(S + 1); read(Q);
    	SAM1 :: build();
    	while(Q --) SAM2 :: main();
    }
    
  • 相关阅读:
    html知识点
    BFC的布局规则以及触发条件
    父元素与子元素之间的margin-top问题(css hack)
    加overflow-hidden就可以解决高度塌陷问题,overflow-触发BFC
    子元素margin-top为何会影响父元素?
    子div设置margin-top使得父div也跟着向下移动
    vue 之 mongodb安装问题
    vue 之 nginx原理(webpack环境下配置)
    vue 之 PC端项目配置
    ASP.NET CORE (一)
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/12760021.html
Copyright © 2011-2022 走看看