暑假集训 ---- 字符串2 (SAM专题)

P1368 工艺
把串插入 S A M SAM SAM 插两次,然后贪心找最小字典序即可
[AHOI2013]差异
两个串的最长公共后缀就是后缀自动机上 lca 的长度
于是把两个串反过来,对于每一个结点统计有多少对结点以它为 lca,size 乘一下就可以了
[TJOI2015]弦论
对于后缀自动机的 DAG,求出 s u m [ i ] sum[i] sum[i] 表示 i 往后走能走多少个字符串
如果不同位置算不同,那么令 s i z i siz_i sizi e n d p o s endpos endpos 集合的大小
如果算相同,那么令 s i z i siz_i sizi 为 1 就好了
[SDOI2016]生成魔咒
正好 S A M SAM SAM可以增量构造,考虑加一个字符的贡献,就是这个点的 m x l e n − m i l e n + 1 mxlen - milen+1 mxlenmilen+1

SPOJ LCS1:一个建 SAM,另一个丢上去跑就好了
SPOJ LCS2:一个建 SAM,其他每个都丢上去跑,记录 m x [ u ] mx[u] mx[u] 表示后缀自动机 u 结点能匹配的最大长度
然后求 m i n ( m x [ u ] ) min(mx[u]) min(mx[u]),等等,当前结点能作为答案,那么 l i n k [ u ] link[u] link[u] 一定可以被走到,而且可以走完
于是倒过来求答案,如果当前可以走到,那么将 m x [ l i n k [ u ] ] mx[link[u]] mx[link[u]] 设为 l e n [ u ] len[u] len[u] 就可以了
BZOJ1396 识别子串
建出 S A M SAM SAM, ∣ R i g h t ∣ |Right| Right 为 1 的就是一个识别子串
这些合法的串的在原串的位置是 [ 1 , 2... l e n [ x ] − l e n [ l i n k [ x ] ] , l e n [ x ] ] [1,2...len[x]-len[link[x]] , len[x]] [1,2...len[x]len[link[x]],len[x]]
考虑这个可以作为哪些位置的识别子串
对于 [ 1 , l e n [ x ] − l e n [ l i n k [ x ] ] [1,len[x]-len[link[x]] [1,len[x]len[link[x]],以它为答案的识别子串长度为 l e n [ x ] − i + 1 len[x]-i+1 len[x]i+1
对于 [ l e n [ x ] − l e n [ l i n k [ x ] ] , l e n [ x ] ] [len[x]-len[link[x]],len[x]] [len[x]len[link[x]],len[x]],以它为答案的识别子串的长度为 l e n [ x ] − l e n [ l i n k [ x ] ] len[x]-len[link[x]] len[x]len[link[x]]
用两棵线段树维护即可
[TJOI2016]字符串
后缀数组做法:二分答案,那么 m i d ≤ h e i g h t mid\le height midheight 对应一段区间,可以二分出来
然后看一下这段区间 [ l , r ] [l,r] [l,r] 内有没有在区间 [ a , b − m i d ] [a,b-mid] [a,bmid] 里面的,用主席树即可
SAM做法:先要把串倒过来
二分答案,找到 [ c , c + m i d − 1 ] [c,c+mid-1] [c,c+mid1] 所在的结点,查询它 ∣ R i g h t ∣ |Right| Right 集合中有无在区间 [ a + m i d − 1 , b ] [a+mid-1,b] [a+mid1,b] 内的结点,写一个线段树合并或者主席树即可
[HAOI2016]找相同字符
题解:传送门
对第一个串建一个SAM, 拿第二个上去跑
考虑跑到一个点的贡献, 就是(当前匹配的长度 nowlen - right 集合的最短长度 minlen) * right 集合的大小
因为跟每个长度在这之间的串都可以匹配 siz 次
然后跳 link, 对于它上方的点的贡献, 就是 (maxlen - minlen + 1) * siz
所以我们记录每个点被考虑的次数, 最后向前统计一遍即可
[TJOI2017]DNA
hash 暴力匹配,每次匹配最多跳 3 次
如果怕被卡可以将两个串接在一起做后缀数组求一个 l c p lcp lcp 加速一下匹配
BZOJ3756 Pty的字符串
建出广义后缀自动机,然后把串放上去跑
跑到一个点的贡献是 ( l e n − m i n l e n ) ∗ ∣ r i g h t ∣ (len-minlen)*|right| (lenminlen)right,然后对于祖先的点,有 ∣ r i g h t ∣ ∗ ( m a x l e n − m i n l e n ) |right|*(maxlen-minlen) right(maxlenminlen),用一个前缀和之类的东西记录当前点到根路径上的 ∣ r i g h t ∣ ∗ ( m a x l e n − m i n l e n ) |right|*(maxlen-minlen) right(maxlenminlen) 的和
关于广义 SAM:只需要将 l a s t last last 改成父亲节点在 S A M SAM SAM 上的对应节点即可

#include<bits/stdc++.h>
#define N 2000050
using namespace std;
int n, la[N], las, node;
int ch[N][3], link[N], len[N], siz[N];
int c[N], a[N];
typedef long long ll;
ll f[N]; char S[N];
void extend(int p, int c){
	if(ch[p][c]){
		int q = ch[p][c];
		if(len[q] == len[p] + 1){ ++siz[q]; las = q; return;}
		int clone = ++node; len[clone] = len[p] + 1; siz[clone] = 1; 
		for(int i = 0; i < 3; i++) ch[clone][i] = ch[q][i];
		link[clone] = link[q]; link[q] = clone;
		for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
		las = clone;
	}
	else{
		int now = ++node; len[now] = len[p] + 1; siz[now] = 1; 
		for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;
		if(p == 0) link[now] = 1;
		else{
			int q = ch[p][c];
			if(len[q] == len[p] + 1) link[now] = q;
			else{
				int clone = ++node; len[clone] = len[p] + 1;
				for(int i = 0; i < 3; i++) ch[clone][i] = ch[q][i];
				link[clone] = link[q]; link[q] = link[now] = clone;
				for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
			}
		} las = now;
	}
}
int main(){
	las = node = la[1] = 1;
	scanf("%d", &n); 
	for(int i = 2; i <= n; i++){
		int x; char c[3]; scanf("%d%s", &x, c);
		extend(la[x], c[0] - 'a'); la[i] = las;
	}
	for(int i = 1; i <= node; i++) c[len[i]]++;
	for(int i = 1; i <= n; i++) c[i] += c[i-1];
	for(int i = node; i >= 1; i--) a[c[len[i]]--] = i;
	for(int i = node; i >= 1; i--){ int now = a[i]; siz[link[now]] += siz[now]; }
	for(int i = 1; i <= node; i++) f[i] = 1ll * (len[i] - len[link[i]]) * siz[i];
	for(int i = 1; i <= node; i++){ int now = a[i]; f[now] += f[link[now]];}
	scanf("%s", S + 1); int l = strlen(S + 1), nowlen = 0, now = 1;
	ll ans = 0;
	for(int i = 1; i <= l; i++){
		int c = S[i] - 'a';
		while(now && !ch[now][c]) now = link[now], nowlen = len[now];
		if(!now) now = 1, nowlen = 0;
		else nowlen++, now = ch[now][c], ans += 1ll * (nowlen - len[link[now]]) * siz[now] + f[link[now]];
	} cout << ans; return 0;
}

BZOJ 4310 跳蚤
首先可以二分答案,然后求出第 k 大的子串的起始位置和末位置
从后往前扫一遍,每次比较两个串的大小,如果不行就切开
比较大小可以用后缀数组求 l c p lcp lcp
另外本质不同的串的个数是 ∑ n − s a i + 1 − h e i g h t i \sum n - sa_i+1-height_i nsai+1heighti

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int sa[N], rk[N], c[N], tp[N], y[N], hi[N];	
int st[N][20], lg[N];
int k, n, m; char S[N];
typedef long long ll;
int ls, rs;
void Sort(){
	for(int i = 0; i <= m; i++) c[i] = 0;
	for(int i = 1; i <= n; i++) c[rk[i]]++;
	for(int i = 1; i <= m; i++) c[i] += c[i-1];
	for(int i = n; i >= 1; i--) sa[c[rk[y[i]]]--] = y[i];
}
void SA(){
	for(int i = 1; i <= n; i++) rk[i] = S[i], y[i] = i; Sort();
	for(int k = 1; k <= n; k <<= 1){
		int ret = 0;
		for(int i = n - k + 1; i <= n; i++) y[++ret] = i;
		for(int i = 1; i <= n; i++) if(sa[i] > k) y[++ret] = sa[i] - k;
		Sort(); swap(rk, tp); rk[sa[1]] = 1; int num = 1;
		for(int i = 2; i <= n; i++){
			if(tp[sa[i]] == tp[sa[i-1]] && tp[sa[i]+k] == tp[sa[i-1]+k])
				rk[sa[i]] = num;
			else rk[sa[i]] = ++num;
		} m = num;
	}
}
void Hi(){
	int k = 0;
	for(int i = 1; i <= n; i++){
		if(rk[i] == 1) continue;
		int j = sa[rk[i]-1]; if(k) k--;
		while(i+k <= n && j+k <= n && S[i+k] == S[j+k]) ++k;
		hi[rk[i]] = k;
	}
	for(int i = 1; i <= n; i++) st[i][0] = hi[i];
	for(int i = 1; (1 << i) <= n; i++)
		for(int j = 1; j + (1 << i) - 1 <= n; j++)
			st[j][i] = min(st[j][i-1], st[j + (1<<(i-1))][i-1]);
}
void FSY(ll k){
	for(int i = 1; i <= n; i++){
		ll now = n - sa[i] + 1 - hi[i];
		if(now < k) k -= now;
		else{ ls = sa[i]; rs = sa[i] + hi[i] + k - 1; break;}
	}
}
int lcp(int x, int y){
	if(x == y) return n - x + 1;
	int l = min(rk[x], rk[y]) + 1, r = max(rk[x], rk[y]), k = lg[r - l + 1];
	return min(st[l][k], st[r-(1<<k)+1][k]);
}
bool cmp(int l1, int r1, int l2, int r2){
	int len1 = r1 - l1 + 1, len2 = r2 - l2 + 1, lc = lcp(l1, l2);
	if(lc >= len2 && len1 > len2) return true;
	if(lc >= len1 && len2 >= len1) return false;
	if(lc >= len1 && lc >= len2) return len1 > len2;
	return S[l1 + lc] > S[l2 + lc];
}
bool check(ll w){
	FSY(w);
	int cnt = 1, las = n;
	for(int i = n; i >= 1; i--){
		if(S[i] > S[ls]) return false;
		if(cmp(i, las, ls, rs)) ++cnt, las = i;
		if(cnt > k) return false;
	} return true;
}
int main(){
	scanf("%d", &k);
	scanf("%s", S + 1); n = strlen(S + 1); m = 127;
	for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
	SA(); Hi(); ll l = 1, r = 0;
	for(int i = 1; i <= n; i++) r += (ll)(n - sa[i] + 1 - hi[i]);
	while(l < r){
		ll mid = (l+r) >> 1;
		if(check(mid)) r = mid; else l = mid + 1;
	} FSY(l); for(int i = ls; i <= rs; i++) cout << S[i]; return 0;
}

BZOJ 3413 匹配
题意:问 O ( n m ) O(nm) O(nm) 的暴力匹配要匹配多少次
首先答案 = = = 匹配次数 + 失配次数
分两种情况讨论:
1.匹配完了就跑了
2.在原串中没有出现
只需要对原串建 S A M SAM SAM 然后判一下出现没有即可
假设出现过并且结尾位置为 p p p
那么答案就是 [ 1 , p − l e n + 1 ] [1,p-len+1] [1,plen+1] 中的每一个 i i i 的后缀与匹配串的 l c p + 1 lcp+1 lcp+1
如果枚举 i 并暴力查的话要凉
然后就有一个很骚的 t r i c k trick trick
我们可以枚举 l c p lcp lcp,并查每种 l c p lcp lcp 出现的次数加起来
显然一个长度为 l e n len len l c p lcp lcp 会在 1 , 2... l e n 1,2...len 1,2...len l e n len len
然后就可以拿匹配串在原串上跑,查询有多少 e n d p o s endpos endpos [ 1 , p − l e n + j ] [1,p-len+j] [1,plen+j],j 为当前已经匹配的长度
如果没有出现的话,把 p − l e n + j p-len+j plen+j 的上限设成 n n n 即可
然后没有出现的失配次数是 n n n,出现过的失配次数是 p − l e n p-len plen

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int n, m; char S[N];
int las, node;
int ch[N][10], link[N], len[N];
int a[N], c[N], mi[N], ed[N];
int rt[N], ls[N << 5], rs[N << 5], sum[N << 5];
struct Segmentree{
	int node;
	void ins(int &x, int l, int r, int p){
		if(!x) x = ++node; ++sum[x];
		if(l == r) return; int mid = (l+r) >> 1;
		if(p <= mid) ins(ls[x], l, mid, p);
		else ins(rs[x], mid+1, r, p);
	}
	int merge(int x, int y){
		if(!x || !y) return x + y;
		int nxt = ++node; sum[nxt] = sum[x] + sum[y];
		ls[nxt] = merge(ls[x], ls[y]);
		rs[nxt] = merge(rs[x], rs[y]); return nxt;
	}
	int query(int x, int l, int r, int L, int R){
		if(!x) return 0;
		if(L<=l && r<=R) return sum[x];
		int mid = (l+r) >> 1, ans = 0;
		if(L<=mid) ans += query(ls[x], l, mid, L, R);
		if(R>mid) ans += query(rs[x], mid+1, r, L, R);
		return ans;
	}
}Seg;
int extend(int id, int c){
	int now = ++node, p = las; len[now] = len[p] + 1;
	for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;
	Seg.ins(rt[now], 1, n, id); ed[now] = id;
	if(p == 0) link[now] = 1;
	else{
		int q = ch[p][c];
		if(len[q] == len[p] + 1) link[now] = q;
		else{
			int cl = ++node; len[cl] = len[p] + 1;
			link[cl] = link[q];
			for(int i = 0; i < 10; i++) ch[cl][i] = ch[q][i];
			link[q] = link[now] = cl;
			for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = cl;
		}
	} las = now;
}
void FSY(int len, int &lim){
	int now = 1;
	for(int i = 1; i <= len; i++){
		int c = S[i] - '0';
		if(!ch[now][c]) return; now = ch[now][c];
	} lim = mi[now];
}
int main(){
	scanf("%d%s", &n, S + 1); las = node = 1;
	for(int i = 1; i <= n; i++) extend(i, S[i] - '0');
	for(int i = 1; i <= node; i++) mi[i] = n + 1;
	for(int i = 1; i <= node; i++) c[len[i]]++;
	for(int i = 1; i <= n; i++) c[i] += c[i-1];
	for(int i = node; i >= 1; i--) a[c[len[i]]--] = i;
	for(int i = node; i >= 1; i--){
		int x = a[i]; 
		if(ed[x]) mi[x] = min(mi[x], ed[x]);
		mi[link[x]] = min(mi[link[x]], mi[x]);
		rt[link[x]] = Seg.merge(rt[link[x]], rt[x]);
	}
	scanf("%d", &m);
	for(int i = 1; i <= m; i++){
		scanf("%s", S + 1);
		int len = strlen(S + 1);
		int lim = n + 1;
		FSY(len, lim);
		ll ans = 0; 
		if(lim != n+1) ans = lim - len;
		else ans = n; // failure
		int now = 1;
		for(int j = 1; j <= len; j++){
			int c = S[j] - '0';
			now = ch[now][c];
			if(!now) break;
			ans += (ll)Seg.query(rt[now], 1, n, 1, (lim == n+1 ? n : lim - len + j));
		} cout << ans << '\n';
	} return 0;
}

[CTSC2012]熟悉的文章
首先可以二分答案然后 check
m x i mx_i mxi 为以 i 结尾的串与文库的最大匹配长度
f i f_{i} fi 表示到 i 的最大匹配长度
f i = m a x ( f j + i − ( j + 1 ) + 1 ) ( j ∈ [ i − m x i , i − l e n ] ] f_i=max(f_j+i-(j+1)+1)(j\in [i-mx_i,i-len]] fi=max(fj+i(j+1)+1)(j[imxi,ilen]]
l e n len len 为当前二分的长度,发现 i − m x i i-mx_i imxi 单调不减,可以单调队列优化
然后对于文库建出广义 S A M SAM SAM 匹配一下即可

#include<bits/stdc++.h>
#define N 1100050
using namespace std;
int ch[N << 1][2], link[N << 1], len[N << 1];
int n, m;
char S[N];
int las, node;
int mx[N];
void extend(int c){
	int now = ++node, p = las; 
	for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;
	if(!p) link[now] = 1;
	else{
		int q = ch[p][c];
		if(len[q] == len[p] + 1) link[now] = q;
		else{
			int cl = ++node; len[cl] = len[p] + 1;
			link[cl] = link[q];
			for(int i = 0; i < 2; i++) ch[cl][i] = ch[q][i];
			link[q] = link[now] = cl;
			for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = cl;
		}
	} las = now;
}
int q[N], l, r, f[N];
bool check(int n, int L){
	l = 1; r = 0; 
	for(int i = 0; i <= n; i++) f[i] = 0; 
	for(int i = L; i <= n; i++){
		while(l <= r && f[q[r]] - q[r] < f[i - L] - (i - L)) r--;
		q[++r] = i - L;
		while(l <= r && q[l] < i - mx[i]) l++;
		f[i] = f[i-1];
		if(l <= r) f[i] = max(f[i], f[q[l]] + i - q[l]);
	} return f[n] * 10 >= n * 9;
}
int main(){
	node = 1;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++){
		scanf("%s", S); int len = strlen(S);
		las = 1; 
		for(int j = 0; j < len; j++) extend(S[j] - '0');
	}
	for(int i = 1; i <= n; i++){
		scanf("%s", S + 1); int L = strlen(S + 1);
		int now = 1, nowlen = 0;
		for(int j = 1; j <= L; j++){
			int c = S[j] - '0';
			while(now && !ch[now][c]) now = link[now], nowlen = len[now];
			if(!now) now = 1, nowlen = 0;
			else now = ch[now][c], ++nowlen; 
			mx[j] = nowlen;
		} 
		int l = 0, r = L;
		while(l < r){
			int mid = (l+r+1) >> 1;
			if(check(L, mid)) l = mid;
			else r = mid - 1;
		} cout << l << '\n';
	} return 0;	
} 

BZOJ 3879 SvT
SA:按 r a n k rank rank 排序,把相邻的 l c p lcp lcp 取出来,一个数的贡献是它乘以它作为最小值的区间个数
用单调栈即可
SAM:两个串的 l c p lcp lcp 是后缀树上的 l c a lca lca,把虚树建出来统计 l c a lca lca 的贡献即可

#include<bits/stdc++.h>
#define N 500050
using namespace std;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
typedef long long ll;
const ll Mod = 23333333333333333ll;
int n, m, q; char S[N];
int sa[N], rk[N], y[N], c[N], tp[N], hi[N], st[N][22], lg[N];
void Sort(){
	for(int i = 0; i <= m; i++) c[i] = 0;
	for(int i = 1; i <= n; i++) c[rk[i]]++;
	for(int i = 1; i <= m; i++) c[i] += c[i-1];
	for(int i = n; i >= 1; i--) sa[c[rk[y[i]]]--] = y[i];
}
void SA(){
	for(int i = 1; i <= n; i++) rk[i] = S[i], y[i] = i; Sort();
	for(int k = 1; k <= n; k <<= 1){
		int ret = 0;
		for(int i = n - k + 1; i <= n; i++) y[++ret] = i;
		for(int i = 1; i <= n; i++) if(sa[i] > k) y[++ret] = sa[i] - k;
		Sort(); swap(rk, tp); rk[sa[1]] = 1; int num = 1;
		for(int i = 2; i <= n; i++){
			if(tp[sa[i]] == tp[sa[i-1]] && tp[sa[i] + k] == tp[sa[i-1] + k])
				rk[sa[i]] = num;
			else rk[sa[i]] = ++num;
		} m = num;
	}
}
void Hi(){
	int k = 0;
	for(int i = 1; i <= n; i++){
		if(rk[i] == 1) continue;
		int j = sa[rk[i] - 1]; if(k) k--;
		while(j + k <= n && i + k <= n && S[j + k] == S[i + k]) k++;
		hi[rk[i]] = k;
	}
	for(int i = 1; i <= n; i++) st[i][0] = hi[i];
	for(int i = 1; (1<<i) <= n; i++) 
		for(int j = 1; j + (1<<i) - 1 <= n; j++)
			st[j][i] = min(st[j][i-1], st[j + (1<<(i-1))][i-1]);
	for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
}
int a[N], b[N], sta[N], l[N], r[N];
int RMQ(int l, int r){ 
	int x = lg[r - l + 1];
	return min(st[l][x], st[r-(1<<x)+1][x]);
}
int main(){
	scanf("%d%d%s", &n, &q, S + 1); m = 127;
	SA(); Hi();
	while(q--){
		int k = read(); 
		for(int i = 1; i <= k; i++) a[i] = rk[read()]; 
		sort(a + 1, a + k + 1);
		k = unique(a + 1, a + k + 1) - (a + 1);
		for(int i = 1; i < k; i++) b[i] = RMQ(a[i] + 1, a[i + 1]);
		int tp = 0;
		b[0] = b[k] = -1; sta[++tp] = 0;
		for(int i = 1; i < k; i++){
			while(tp && b[sta[tp]] > b[i]) tp--;
			l[i] = sta[tp]; sta[++tp] = i;
		} tp = 1; sta[tp] = k;
		for(int i = k - 1; i >= 1; i--){
			while(tp && b[sta[tp]] >= b[i]) tp--;
			r[i] = sta[tp]; sta[++tp] = i;
		}
		ll ans = 0;
		for(int i = 1; i < k; i++) ans = (ans + 1ll * (i - l[i]) * (r[i] - i) * b[i]) % Mod;
		cout << ans << '\n';
	}
}

[ZJOI2015]诸神眷顾的幻想乡
考虑到叶子结点只有不超过 20 个,而一条路径仅在以一个叶子为根的树中为从上到下的路径
于是从每个叶子开始做一次广义后缀自动机,一个结点的贡献是 l e n i − l e n l i n k i len_i - len_{link_i} lenilenlinki

#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
int read(){
	int cnt = 0, f = 1; char ch = 0;
	while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}
	while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();
	return cnt * f;
}
int first[N], nxt[N], to[N], du[N], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y; ++du[x]; }
int n, C;
int col[N];
int ch[N * 20][12], link[N * 20], len[N * 20];
int node;
int extend(int c, int p){
	if(ch[p][c]){
		int q = ch[p][c]; 
		if(len[q] == len[p] + 1) return q;
		int clone = ++node; len[clone] = len[p] + 1;
		for(int i = 0; i <= C; i++) ch[clone][i] = ch[q][i];
		link[clone] = link[q]; link[q] = clone;
		for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
		return clone;
	}
	else{
		int now = ++node; len[now] = len[p] + 1;
		for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;
		if(p == 0) link[now] = 1;
		else{
			int q = ch[p][c]; 
			if(len[q] == len[p] + 1) link[now] = q;
			else{
				int clone = ++node; len[clone] = len[p] + 1;
				link[clone] = link[q];
				for(int i = 0; i <= C; i++) ch[clone][i] = ch[q][i];
				link[q] = link[now] = clone; 
				for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
			}
		} return now;
	}
}
void dfs(int u, int fa, int las){
	int p = extend(col[u], las);
	for(int i = first[u]; i; i = nxt[i]){
		int t = to[i]; if(t == fa) continue;
		dfs(t, u, p);
	}
}
int main(){
	n = read(); C = read();
	for(int i = 1; i <= n; i++) col[i] = read();
	for(int i = 1; i < n; i++){
		int x = read(), y = read();
		add(x, y); add(y, x);
	} 
	node = 1;
	for(int i = 1; i <= n; i++) if(du[i] == 1) dfs(i, 0, 1);
	ll ans = 0;
	for(int i = 2; i <= node; i++) ans += (ll)len[i] - (ll)len[link[i]];
	cout << ans; return 0;
}

BZOJ 5084 hashit
考虑到本质不同的子串数为
∑ n − s a [ i ] + 1 − h e i g h t [ i ] \sum n - sa[i]+1-height[i] nsa[i]+1height[i]
需要动态维护 s a , r a n k sa, rank sa,rank
把串倒过来插就是后缀平衡树的模板
插入或删除过后需要重新获得 h e i g h t height height,二分+ h a s h hash hash 求一个 l c p lcp lcp 就好

#include<bits/stdc++.h>
#define N 100050
using namespace std;
typedef long long ll;
const ll inf = 1e18;
typedef unsigned long long ull;
int Rnd(){ return rand() | (rand() << 15);}
char s[N];
int hi[N], rnd[N];
int rt, ch[N][2]; ll val[N];
int n, len, siz[N]; ll ans;
const ull Base = 5261023;
ull hash[N], pw[N];

void pia(int x, ll l, ll r){
	if(!x) return; ll mid = l + r >> 1;
	val[x] = mid; pia(ch[x][0], l, mid-1); pia(ch[x][1], mid+1, r);
	siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + 1;
}
void rot(int &x, int y, ll l, ll r){
	return;
	int k = ch[x][1] == y; ch[x][k] = ch[y][k^1]; 
	ch[y][k^1] = x; x = y; pia(x, l, r);
}
bool cmp(int x, int y){ return s[x] < s[y] || (s[x] == s[y] && val[x - 1] < val[y - 1]);}
void Add(int &x, ll l, ll r){
	if(!x){ 
		x = len; val[x] = (l + r) >> 1; siz[x] = 1; rnd[x] = Rnd();
		ch[x][0] = ch[x][1] = 0; return;
	} 
	ll mid = l + r >> 1; ++siz[x];
	if(cmp(len, x)){ Add(ch[x][0], l, mid-1); if(rnd[x] > rnd[ch[x][0]]) rot(x, ch[x][0], l, r); }
	else { Add(ch[x][1], mid+1, r); if(rnd[x] > rnd[ch[x][1]]) rot(x, ch[x][1], l, r); }
}
int rk(int x, int k){
	if(x == k) return siz[ch[x][0]] + 1;
	if(cmp(k, x)) return rk(ch[x][0], k);
	else return rk(ch[x][1], k) + siz[ch[x][0]] + 1;
}
int kth(int x, int k){
	if(!x) return 0;
	if(siz[ch[x][0]] >= k) return kth(ch[x][0], k);
	else if(siz[ch[x][0]] + 1 == k) return x;
	else return kth(ch[x][1], k - siz[ch[x][0]] - 1);
}
bool ck(int l1, int l2, int len){
	return (hash[l1] - hash[l1 - len] * pw[len]) == (hash[l2] - hash[l2 - len] * pw[len]);
}
int lcp(int x, int y){
	int l = 0, r = min(x, y);
	while(l < r){
		int mid = (l+r+1) >> 1;
		if(ck(x, y, mid)) l = mid;
		else r = mid - 1;
	} return l;	
}
void ins(int k){
	s[++len] = s[k]; hash[len] = hash[len - 1] * Base + s[k];
	Add(rt, 1, inf);
	int a = rk(rt, len), b = kth(rt, a - 1), c = kth(rt, a + 1);
	if(c) ans -= hi[c]; 
	if(c) hi[c] = lcp(len, c); 
	if(b) hi[len] = lcp(b, len);
	ans += hi[len] + hi[c];
}
int merge(int x, int y){
	if(!x || !y) return x + y;
	if(rnd[x] < rnd[y]){ ch[x][1] = merge(ch[x][1], y); return x;}
	else { ch[y][0] = merge(x, ch[y][0]); return y;}
}
void Del(int &x, ll l, ll r){
	if(x == len){
		x = merge(ch[x][0], ch[x][1]);
		pia(x, l, r); return;
	}
	--siz[x]; ll mid = l + r >> 1;
	if(cmp(len, x)) Del(ch[x][0], l, mid-1); 
	else Del(ch[x][1], mid+1, r);
}
void del(){
	int a = rk(rt, len);
	int b = kth(rt, a - 1), c = kth(rt, a + 1);
	ans -= hi[len] + hi[c];
	hi[c] = lcp(b, c);
	ans += hi[c]; Del(rt, 1, inf); len--;	
}
int main(){
	srand(time(0));
	scanf("%s", s + 1); n = strlen(s + 1);
	pw[0] = 1; for(int i = 1; i <= n; i++) pw[i] = pw[i-1] * Base;
	for(int i = 1; i <= n; i++){
		if(s[i] == '-') del(); else ins(i);
		cout << 1ll * len * (len + 1) / 2 - ans << '\n';
	} return 0;
}

SP8093 JZPGYZ - Sevenk Love Oimaster
建出广义后缀自动机后,把询问串拿上去跑
如果我们对每个模板串的结点标记一个 i d id id 的话,它的答案就是 p a r e n t parent parent 树子树中 i d id id 的个数
因为它子树的所有结点都包涵它
于是处理出 d f s dfs dfs 序后就可以转换为查询区间颜色个数,树状数组即可

#include<cstdio>
#include<string>
#include<vector>
#include<iostream>
#define N 800050
using namespace std;
int n, m, las, node;
int ch[N][26], link[N], len[N];
string S;
vector<int> a[N];
vector<int> v[N];
void extend(int id, int c){
	if(ch[las][c]){
		int p = las, q = ch[p][c];
		if(len[q] == len[p] + 1){ a[q].push_back(id); las = q; return;}
		int clone = ++node; len[clone] = len[p] + 1; 
		a[clone].push_back(id); link[clone] = link[q];
		for(int i = 0; i < 26; i++) ch[clone][i] = ch[q][i];
		link[q] = clone;
		for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
		las = clone; return;
	}
	int now = ++node, p = las; len[now] = len[p] + 1;
	a[now].push_back(id);
	for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;
	if(!p) link[now] = 1;
	else{
		int q = ch[p][c]; 
		if(len[q] == len[p] + 1) link[now] = q;
		else{
			int clone = ++node; len[clone] = len[p] + 1;
			link[clone] = link[q];
			for(int i = 0; i < 26; i++) ch[clone][i] = ch[q][i];
			link[q] = link[now] = clone;
			for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;
		}
	} las = now;
}
int st[N], ed[N], sign, cnt, ans[N];
struct data{ int l, op, id; data(int _l = 0, int _op = 0, int _id = 0){l = _l, op = _op, id = _id;} };
vector<data> q[N];
int vis[N], pre[N], pos[N];
void dfs(int u){
	st[u] = ++sign; pos[sign] = u;
	for(int i = 0; i < v[u].size(); i++) dfs(v[u][i]); ed[u] = sign;
}
int c[N];
void Add(int x, int v){ ++x; for(;x<=sign+1; x+=x&-x) c[x] += v;}
int Ask(int x){ ++x; int ans = 0; for(;x;x-=x&-x) ans += c[x]; return ans;}
int main(){
	scanf("%d%d", &n, &m); node = 1;
	for(int i = 1; i <= n; i++){
		cin >> S; int len = S.length(); las = 1;
		for(int j = 0; j < len; j++) extend(i, S[j] - 'a');
	}
	for(int i = 2; i <= node; i++) v[link[i]].push_back(i);
	dfs(1);
	for(int i = 1; i <= m; i++){
		cin >> S; int len = S.length(); 
		int now = 1;
		for(int j = 0; j < len; j++){
			now = ch[now][S[j]-'a']; if(now == 0) break;
		} 
		q[st[now]-1].push_back(data(st[now] - 1, -1, i));
		q[ed[now]].push_back(data(st[now] - 1, 1, i));
	}
	for(int i = 1; i <= sign; i++){
		for(int j = 0; j < a[pos[i]].size(); j++){
			int now = a[pos[i]][j]; pre[now] = vis[now]; vis[now] = i;
			Add(pre[now], 1); 
		}
		for(int j = 0; j < q[i].size(); j++){
			data now = q[i][j];
			ans[now.id] += now.op * Ask(now.l);
		}
	}
	for(int i = 1; i <= m; i++) cout << ans[i] << '\n';
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值