[后綴自動機初探]


具體的定義及基本應用構造見2012年冬令營陳老師的ppt

這篇博文的題目對於剛剛接觸的同學有可能偏難,建議可以用后綴自動機做一下以前做過的后綴數組的題目。不過題目都是很好的!

[POJ 2774]Long Long Message

后綴自動機的模式匹配。

類似kmp一樣的往上跳

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 100010
using namespace std;

char str[maxn];

struct Node{
	int len, link, nxt[26];
}st[maxn << 1];
int root, size, last;
void init(){
	root = size = last = 0;
	st[root].len = 0;
	st[root].link = -1;
}

void Extend(int c){
	int p = last, cur = ++ size;
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

int main(){
	init();
	scanf("%s", str + 1);
	int n = strlen(str + 1);
	for(int i = 1; i <= n; i ++)
	    Extend(str[i] - 'a');

	scanf("%s", str + 1);
	n = strlen(str + 1);
	int nw = root, cur = 0, ans = 0;
	for(int i = 1; i <= n; i ++){
		int c = str[i] - 'a';
		if(st[nw].nxt[c])cur ++, nw = st[nw].nxt[c];
		else{
			while(~nw && st[nw].nxt[c] == 0)
				nw = st[nw].link;
			if(nw == -1)nw = root, cur = 0;
			else cur = st[nw].len + 1, nw = st[nw].nxt[c];
		}
		ans = max(ans, cur);
	}
	printf("%d\n", ans);
	return 0;
}

[BZOJ 3238][AHOI 2013]差異

給一個字符串,求∑ ∑ len[i] + len[j] - 2 * lcp(i, j)

差異這道題目給初學sam的窩很大啟發

求兩個子串的lcp的方法:

將原串逆序插入后綴自動機即得后綴樹。

將兩個點的LCA求出,LCA對應的len值即為LCP的長度。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1000010
using namespace std;

int n;

typedef long long ll;

char s[maxn];

struct Node{int len, link, nxt[26];}st[maxn];

int root, size, last;

void init(){
	root = size = last = 0;
	st[root].link = -1;
}

void Extend(char ch){
	int p = last, cur = ++ size, c = ch - 'a';
	st[cur].len = st[last].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[p].len + 1 == st[q].len)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

long long ans;

int mark[maxn];

struct Edge{
	int to, next;
}edge[maxn];

int h[maxn], cnt;

void add(int u, int v){
	cnt ++;
	edge[cnt].to = v;
	edge[cnt].next = h[u];
	h[u] = cnt;
}

int dep[maxn];
ll dp[maxn], ret;

void dfs(int u){
	dp[u] = mark[u];
	ll sum = dp[u] * dp[u];
	for(int i = h[u]; i; i = edge[i].next){
		int v = edge[i].to;
		dfs(v);
		dp[u] += dp[v];
		sum += dp[v] * dp[v];
	}
	ret += (dp[u] * dp[u] - sum) * st[u].len;
}

long long solve(){
	int now = root;
	for(int i = n; i >= 1; i --){
		now = st[now].nxt[s[i] - 'a'];
		mark[now] ++;
	}
	for(int i = 1; i <= size; i ++)
	    add(st[i].link, i);
	ret = 0;
	dfs(root);
	return ret;
}

ll p[maxn];

int main(){
	scanf("%s", s + 1);
	n = strlen(s + 1);
	init();
	for(int i = n; i >= 1; i --)
	    Extend(s[i]);
	for(int i = 1; i <= n; i ++)
	    p[i] = p[i - 1] + i;
	for(int i = n; i >= 1; i --)
	    ans += 1ll * i * (i - 1) + p[i - 1];

	printf("%lld\n", ans - solve());
	return 0;
}

[BZOJ 3676][APIO 2014]回文串

考慮一個只包含小寫拉丁字母的字符串s。我們定義s的一個子串t的“出現值”為t在s中的出現次數乘以t的長度。請你求出s的所有回文子串中的最大出現值。 

后綴自動機上的倍增(一種常用的技巧),當然了,可以用pam

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 600010
using namespace std;

char s[maxn];
int n;
long long ans;

struct Node{int len, link, nxt[26], size;}st[maxn];

int root, size, last;

void init(){
	root = size = last = 0;
	st[root].len = 0;
	st[root].link = -1;
}

int anc[maxn][20], pos[maxn];

void Extend(char ch, int part){
	int p, cur = ++ size, c = ch - 'a';
	st[cur].len = st[last].len + 1;
	st[cur].size = 1;
	pos[part] = cur;
	for(p = last; ~p && !st[p].nxt[c]; p = st[p].link)
	    st[p].nxt[c] = cur;
	pos[part] = cur;
	st[cur].size = 1;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			st[clone].size = 0;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

int t[maxn], w[maxn];

void build(){
	memset(anc, -1, sizeof anc);
	for(int i = 1; i <= size; i ++)
	    anc[i][0] = st[i].link;
	for(int j = 1; 1 << j <= size; j ++)
	    for(int i = 1; i <= size; i ++){
			int a = anc[i][j - 1];
			if(~a)anc[i][j] = anc[a][j - 1];
	    }

	for(int i = 1; i <= size; i ++)
		w[st[i].len] ++;
    for(int i = 1; i <= size; i ++)
		w[i] += w[i - 1];
	//for(int i = 1; i <= size; i ++)
	for(int i = size; i >= 1; i --)
		t[w[st[i].len] --] = i;
	for(int i = size; i; i --)
		st[st[t[i]].link].size += st[t[i]].size;
}

void update(int l, int r){
	int t = pos[r];
	for(int i = 18; i >= 0; i --){
		if(~anc[t][i]){
			int to = anc[t][i];
			if(st[to].len >= r - l + 1)
			    t = to;
		}
	}
	ans = max(ans, 1ll * st[t].size * (r - l + 1));
}

int r[maxn];

void solve(){
	s[0] = '*';
	s[n + 1] = '#';
	init();
	for(int i = 1; i <= n; i ++)
	    Extend(s[i], i);
	build();

	int mx = 0, p = 0;
	for(int i = 1; i <= n; i ++){
		if(i < mx)r[i] = min(r[2 * p - i - 1], mx - i);
		else r[i] = 0;
		while(s[i + r[i] + 1] == s[i - r[i]]){
            r[i] ++;
			update(i - r[i] + 1, i + r[i]);
		}
		if(r[i] + i > mx)mx = r[i] + i, p = i;
	}

	mx = 0, p = 0;
	for(int i = 1; i <= n; i ++){
		if(i < mx){r[i] = min(r[2 * p - i], mx - i - 1);}
		else {r[i] = 1;update(i, i);}
		while(s[i + r[i]] == s[i - r[i]]){
            r[i] ++;
			update(i - r[i] + 1, i + r[i] - 1);
		}
		if(r[i] + i > mx)mx = r[i] + i, p = i;
	}

	printf("%lld\n", ans);
}

int main(){
	scanf("%s", s + 1);
	n = strlen(s + 1);
	solve();
	return 0;
}

其實還有一道題目相關--HEOI2015最短不公共子串

[BZOJ 3998]弦論

對於一個給定長度為N的字符串,求它的第K小子串是什么。

 

第一行是一個僅由小寫英文字母構成的字符串S

 

第二行為兩個整數T和K,T為0則表示不同位置的相同子串算作一個。T=1則表示不同位置的相同子串算作多個。K的意義如題所述。
輸出僅一行,為一個數字串,為第K小的子串。如果子串數目不足K個,則輸出-1

 

后綴自動機(大概是子串計數一道很好的題目)

SAM上每一個節點代表一條路徑從根出發到這里的字符串。

所以每一個節點++就代表一個不同的子串

right集合:parent樹上所對應的葉子節點的個數。

然后我們要把right集合累加起來當做位置不同的子串算多個的個數。

然后DFS。(26分?霧。。)

后綴自動機的狀態right集合大小是其在parent樹中子樹的葉子節點數量,代表這個狀態所代表的字串出現次數。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1000010
using namespace std;
char c[maxn];
struct Node{
	int len, link, nxt[26];
}st[maxn];

int root, last, size;

void init(){
	root = last = size = 1;
	st[root].len = 0;
	st[root].link = -1;
}
int t[maxn], w[maxn], sum[maxn], val[maxn];
void Extend(char ch){
	int p, cur = ++ size, c = ch - 'a';
	st[cur].len = st[last].len + 1;
	val[cur] = 1;
	for(p = last; ~p && !st[p].nxt[c]; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
				st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}last = cur;
}

int x, k;



void prepare(){
	for(int i = 1; i <= size; i ++)w[st[i].len] ++;
	for(int i = 1; i <= size; i ++)w[i] += w[i - 1];
	for(int i = size; i >= 1; i --)t[w[st[i].len] --] = i;
	for(int i = size; i >= 1; i --){
		int now = t[i];
		if(x == 1)val[st[now].link] += val[now];
		else val[now] = 1;
	}
	val[1] = 0;
	for(int i = size; i; i --){
		int now = t[i];sum[now] = val[now];
		for(int j = 0; j < 26; j ++)
		    sum[now] += sum[st[now].nxt[j]];
	}
}

void dfs(int x, int k){
	if(k <= val[x])return;
	k -= val[x];int v;
	for(int i = 0; i < 26; i ++){
		if(v = st[x].nxt[i]){
			if(k <= sum[v]){
				putchar(i + 'a');
				dfs(v, k);
				return;
			}
			k -= sum[v];
		}
	}
}

int main(){
	init();
	scanf("%s", c + 1);
	int n = strlen(c + 1);
	for(int i = 1; i <= n; i ++)
	    Extend(c[i]);
	scanf("%d%d", &x, &k);
	prepare();
	if(sum[1] < k)printf("-1");
	else dfs(1, k);
	return 0;
}

[NOI 2015]品酒大會

給定一個字符串,求出這個字符串中所有長度為i(0<=i<n)兩兩相等的子串個數和給定value[p]*value[q]的最大值(p,q為左端點)

 

考慮后綴自動機。

如果將所有節點(包括clone節點)的路徑數都賦為1。做路徑計數的話應該是所有本質不同的子串的計數。

如果只將原字符串遍歷到的節點(不包括clone節點)的值賦為1,做路徑計數的話應該是所有節點的right集合。

根據[TJOI 弦論]如果把right集合累加起來,就可以得知子串相同但是位置不同算多個的子串的個數

right集合代表的是什么?

是指parent樹上這個點子樹的葉節點的個數。是這個狀態的子串在原串中出現的次數

這道題目:選擇兩個子串,它們的LCP等於parent樹上的LCA的len值。

我們要統計的是right集合(代表這個狀態的字符串出現的次數)。也就是這棵子樹中的葉節點的數目。

關於Right集合:

定義:一個子串str在母串S中所有出現位置的右端點。如子串str在S中出現位置為[l1,r1),[l2,r2),...,[ln,r3),則 str的Right集合為{r1..rn}。會有一些子串的Right集合相同,其中最長的len為MAX(s),最短的為MIN(s)

#include <bits/stdc++.h>
#define maxn 600010
using namespace std;

int n;

char str[maxn];

typedef long long ll;

ll ans[maxn], mx[maxn][2], mn[maxn][2], s[maxn], siz[maxn], t;

int val[maxn];

struct Node{
	int nxt[26], len, link;
}st[maxn];

int last, size, root;

void init(){
	root = last = size = 0;
	st[root].link = -1;
	st[root].len = 0;
}

void Extend(char ch, int Id){
	int cur = ++ size, p = last, c = ch - 'a';
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
	mx[cur][0] = mn[cur][0] = val[Id];
	siz[cur] = 1;
}

struct Edge{
	int to, next;
}edge[maxn << 1];
int h[maxn], cnt;
void add(int u, int v){
	cnt ++;
	edge[cnt].to = v;
	edge[cnt].next = h[u];
	h[u] = cnt;
}

inline void upd(ll& a, ll b){
	if(b > a)
		a = b;
}
const ll inf = 1e9+1;


void DFS(int u){
	int len = st[u].len;
	for(int i = h[u]; i; i = edge[i].next){
		int v = edge[i].to;
		DFS(v);
		s[len] += siz[u] * siz[v];
		siz[u] += siz[v];
		if(mx[v][0] >= mx[u][0]){
			mx[u][1] = mx[u][0];
			mx[u][0] = mx[v][0];
		}
		else mx[u][1] = max(mx[u][1], mx[v][0]);

  		if(mn[v][0] <= mn[u][0]){
			mn[u][1] = mn[u][0];
			mn[u][0] = mn[v][0];
		}
		else mn[u][1] = min(mn[u][1], mn[v][0]);

		mx[u][1] = max(mx[u][1], mx[v][1]);
		mn[u][1] = min(mn[u][1], mn[v][1]);
	}
	if(mx[u][1] > -inf && mx[u][1] > -inf)upd(ans[len], mx[u][0] * mx[u][1]);
	if(mn[u][1] < inf && mn[u][0] < inf)upd(ans[len], mn[u][0] * mn[u][1]);
}

int main(){
	init();
	scanf("%d", &n);
	scanf("%s", str+1);
	for(int i = 1; i <= n; i ++)
	    scanf("%d", &val[i]);
	for(int i = 0; i <= 2 * n; i ++){
		ans[i] = -1ll << 61;
		mx[i][0] = mx[i][1] = -0x7fffffff;
		mn[i][0] = mn[i][1] = 0x7fffffff;
	}
	for(int i = n; i >= 1; i --)
	//for(int i = 1; i <= n; i ++)
	    Extend(str[i], i);
	for(int i = 1; i <= size; i ++)
	    add(st[i].link, i);
    
	DFS(root);
	for(int i = n-2; i >= 0; i --)
	    ans[i] = max(ans[i+1], ans[i]), s[i] += s[i+1];
	
	for(int i = 0; i < n; i ++){
		if(s[i] == 0)puts("0 0");
		else printf("%lld %lld\n", s[i], ans[i]);
	}
	return 0;
}

[BZOJ 4310]跳蚤

題目請點上面的鏈接

詳細題解在這里

如何求第k大的子串?

26分?逐位確定

如果k大於這個兒子的個數就減掉

否則k-=當前的字符串值(如果是本質不同的字符串減1,否則減掉當前狀態代表的值)然后轉移now即可

當k=0時停止。

#include <bits/stdc++.h>
#define maxn 200010
using namespace std;

int n, k;
struct Node{int len, link, nxt[26];}st[maxn];
long long s[maxn];

char str[maxn];

int root, size, last;

void init(){
	root = last = size = 0;
	st[root].link = -1;
	st[root].len = 0;
}

void Extend(char ch){
	int cur = ++ size, p = last, c = ch - 'a';
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

bool vis[maxn];

long long DFS(int u){
	if(vis[u])return s[u];
	s[u] = vis[u] = 1;
	for(int i = 0; i < 26; i ++)
		if(st[u].nxt[i])
			s[u] += DFS(st[u].nxt[i]);
	return s[u];
}

int m;
char ans[maxn];
void Getstring(long long k){
	int now = root, t; m = 0;
	while(true){
		for(int i = 0; i < 26; i ++){
			if((t = st[now].nxt[i]) == 0)continue;
			if(k > s[t])
				k -= s[t];
			else{
				now = t, k --;
				ans[++ m] = i + 'a';
				if(k == 0)return;
				break;
			}
		}
	}
}

unsigned long long bases[maxn], hash1[maxn], hash2[maxn];
#define base 13131

bool pd(int i, int len){
	if(len == 0)return true;
	return hash1[1] - hash1[1+len] * bases[len] == hash2[i] - hash2[i+len] * bases[len];
}

bool cmp(int i, int j){
	if(str[i] < ans[1])return true;
	if(str[i] > ans[1])return false;
	int l = 1, r = min(m, j-i+1);
	while(l < r){
		int mid = l + (r - l + 1) / 2;
		if(pd(i, mid-1)){
			if(str[i+mid-1] < ans[mid])return true;
			if(str[i+mid-1] > ans[mid])return false;
			l = mid;
		}
		else r = mid - 1;
	}
	if(str[i+r-1] < ans[r])return true;
	if(str[i+r-1] > ans[r])return false;
	if(j-i+1 > m)return false;
	return true;
}

bool check(long long where){
	Getstring(where);
	hash1[m+1] = 0;
	for(int i = m; i >= 1; i --)
	    hash1[i] = hash1[i+1] * base + ans[i];
	int pos = n, cnt = 0;
	for(int i = n; i; i = pos){
		while(pos && cmp(pos, i))
		    pos --;
		cnt ++;
		if(cnt > k || pos == i)return false;
	}return true;
}

int main(){
	scanf("%d%s", &k, str+1);
	init(); n = strlen(str+1);
	for(int i = 1; i <= n; i ++)
	    Extend(str[i]);
	DFS(root);
	bases[0] = 1;
	for(int i = n; i >= 1; i --)
	    hash2[i] = hash2[i+1] * base + str[i];
	for(int i = 1; i <= n; i ++)
	    bases[i] = bases[i-1] * base;
	long long l = 1, r = s[root];
	while(l < r){
		long long mid = l + r >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	Getstring(r);
	for(int i = 1; i <= m; i ++)
	    putchar(ans[i]);
	return 0;
}

[HAOI 2016]找相同字符

我寫了一個非常鬼(ma)畜(fan)的做法。。

建立一個a串的后綴自動機,把b串放上去跑,在跑到的節點上累加答案

注意到后綴自動機一個節點包括的狀態有很多(根本沒注意)

狀態有(len - fa_len)這么多種

而且b串跑的長度並不是當前狀態的len

額外記錄一下

其實建個廣義后綴自動機直接統計就好了啊喂

 

#define MAXN 500000
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

int n1, n2;
char a[MAXN], b[MAXN];

int root, last, size;
struct Node { int len, link, nxt[26]; } st[MAXN];
void init() {
	root = last = size = 0;
	st[root].link = -1;
	st[root].len = 0;
}

void Extend(int c) {
	int cur = ++ size, p = last;
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
		st[p].nxt[c] = cur;
	if(p == -1)
		st[cur].link = root;
	else {
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
			st[cur].link = q;
		else {
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
				st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	} last = cur;
}

int t[MAXN], w[MAXN];
ll s[MAXN], sz[MAXN];

int main() {
	freopen("find_2016.in", "r", stdin);
	freopen("find_2016.out", "w", stdout);
	scanf("%s%s", a+1, b+1);
	n1 = strlen(a+1), n2 = strlen(b+1);
	init();
	for(int i = 1; i <= n1; ++ i)
		Extend(a[i]-'a');
	int cur = root, step = 0;
	for(int i = 1; i <= n1; ++ i) cur = st[cur].nxt[a[i]-'a'], s[cur] ++;
	for(int i = 1; i <= size; ++ i) w[st[i].len] ++;
	for(int i = 1; i <= size; ++ i) w[i] += w[i-1];
	for(int i = 1; i <= size; ++ i) t[w[st[i].len] --] = i;
	for(int i = size; i >= 1; -- i) s[st[t[i]].link] += s[t[i]];
	for(int i = 1; i <= size; ++ i) sz[i] = s[i];
	for(int i = 1; i <= size; ++ i) s[i] = s[i] * (st[i].len-st[st[i].link].len);
	s[cur = root] = 0;
	for(int i = 1; i <= size; ++ i) s[t[i]] += s[st[t[i]].link];
	
	long long ans = 0; 
	for(int i = 1; i <= n2; ++ i) {
		int c = b[i]-'a';
		if(st[cur].nxt[c]) cur = st[cur].nxt[c], step ++;
		else {
			while(~cur && !st[cur].nxt[c]) cur = st[cur].link;
			if(~cur) step = st[cur].len + 1, cur = st[cur].nxt[c];
			else cur = root, step = 0;
		}
		ans += s[st[cur].link] + sz[cur]*(step - st[st[cur].link].len);
	} printf("%lld\n", ans);
	return 0;
}

[BZOJ 3277] 串

現在給定你n個字符串,詢問每個字符串有多少子串(不包括空串)是所有n個字符串中至少k個字符串的子串(注意包括本身)。

Sol:

建立廣義后綴自動機,然后給每個串打上標記(不卡時我就暴力改了一下)

掃一遍每一個串,統計當前字符結尾的子串>=k的個數,記憶搜即可

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 200010
using namespace std;

int n, k;
char s[maxn];
int in[maxn], out[maxn];

struct Node{
	int len, link, nxt[27];
}st[maxn << 1];
int root, size, last;

void init(){
	last = root = size = 0;
	st[root].link = -1;
	st[root].len = 0;
}

void Extend(int c){
	int p = last, q = st[p].nxt[c];
	if(q){
		if(st[q].len == st[p].len + 1)
		    last = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = clone;
			last = clone;
		}
		return;
	}
	int cur = ++ size;
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
		st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[cur].link = st[q].link = clone;
		}
	}
	last = cur;
}

int vis[maxn];

int total_vis[maxn];
void update(int rt, int y){
	while(~rt){
		if(vis[rt] == y)break;
		total_vis[rt] ++;
		vis[rt] = y;
		rt = st[rt].link;
	}
}


long long val[maxn];
long long dfs(int rt){
	if(vis[rt])return val[rt];
	vis[rt] = true;
	long long ret = 0;
	if(~st[rt].link)ret = dfs(st[rt].link);
	if(total_vis[rt] >= k)
		ret += (long long)st[rt].len - (st[rt].link == -1 ? 0 : st[st[rt].link].len);
	return val[rt] = ret;
}

int main(){
	init();
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= n; i ++){
		in[i] = out[i-1] + 1;
		scanf("%s", s+in[i]);
		out[i] = strlen(s+in[i]) + in[i] - 1;

		last = root;
		for(int j = in[i]; j <= out[i]; j ++)
		    Extend(s[j] - 'a');
	}
	
	for(int i = 1; i <= n; i ++){
		int now = root;
		for(int j = in[i]; j <= out[i]; j ++)
		    now = st[now].nxt[s[j] - 'a'], update(now, i);
	}
	memset(vis, 0, sizeof vis);
	for(int i = 1; i <= n; i ++){
		long long ans = 0;
		int now = root;
		for(int j = in[i]; j <= out[i]; j ++){
			now = st[now].nxt[s[j] - 'a'];
			ans += dfs(now);
		}
		printf("%I64d ", ans);
	}
	return 0;
}

[BZOJ 2806]Cheat

小強和阿米巴是好盆友~~

我們可以預處理出l[i]表示以i結尾的后綴在所有字符串中的最長匹配長度

如何得到呢?廣義后綴自動機。

剩下用單調隊列優化dp就可以了。

#include <bits/stdc++.h>

using namespace std;

#define maxn 1100010
int n, m;
char str[maxn];
int last, root, size;
struct Node{int link, len, nxt[2];}st[maxn << 1];
void init(){
	last = root = size = 0;
	st[root].len = 0;
	st[root].link = -1;
}

void Extend(int c){
	int p = last, q;
	if(q = st[p].nxt[c]){
		if(st[q].len == st[p].len + 1)
		    last = q;
		else{
            int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = clone;
			last = clone;
		}return;
	}
	int cur = ++ size;
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

//dp[i] = max(dp[j] + match(i, j), dp[i-1])
//max(dp[j] - j) + i  j∈[i - st[nw].len, i - L]
int dp[maxn], que[maxn], val[maxn], head, tail;
int len, l[maxn];

void match(){
    len = strlen(str + 1);
    int nw = root, cur = 0;
    for(int i = 1; i <= len; i ++){
		int c = str[i] == '1';
		if(st[nw].nxt[c])cur ++, nw = st[nw].nxt[c];
		else{
			while(~nw && !st[nw].nxt[c])
			    nw = st[nw].link;
			if(nw == -1) nw = root, cur = 0;
			else cur = st[nw].len + 1, nw = st[nw].nxt[c];
		}
		l[i] = cur;
    }
}

bool check(int L){
	dp[0] = 0;
	head = tail = 0;
	for(int i = 1; i <= len; i ++){
		dp[i] = dp[i - 1];
		int p = i - L;
		if(p >= 0){
			int v = dp[p] - p;
			while(head < tail && v > val[tail - 1])
			    tail --;
			que[tail] = p;
			val[tail] = v;
			tail ++;
		}
		while(head < tail && que[head] + l[i] < i)
		    head ++;
		if(head < tail)dp[i] = max(dp[i], val[head] + i);
	}
	return 10 * dp[len] >= 9 * len;
}

void Getans(){
	int l = 0, r = len;
	while(l < r){
		int mid = l + (r - l + 1) / 2;
		if(check(mid)) l = mid;
		else r = mid - 1;
	}
	printf("%d\n", l);
}

int main(){
	init();
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i ++){
		scanf("%s", str + 1);
		len = strlen(str + 1), last = root;
		for(int j = 1; j <= len; j ++)
		    Extend(str[j] == '1');
	}
	
	for(int i = 1; i <= n; i ++){
		scanf("%s", str + 1);
		match();
		Getans();
	}
	return 0;
}

[BZOJ 1396] 識別子串

用后綴自動機搞出出現了一次的子串(其實就是求每個節點的Right)

記錄每個節點的r(及他在原串中出現的pos位置,由於所求是Right=1的位置,所以pos唯一)

每一個節點的長度區間為[fa[len] + 1, len],即長度最小以及最大的[min, max]

發現有兩種更新方式,在長度為[len - fa[len] + 1,len]這段區間要用len-fa[len]+1, len-fa[len]+2,.......,len來更新,在長度為[0, len - fa[len]]這一段區間要用fa[len] + 1來更新

線段樹維護一下就可以了

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;
struct Node{int len, link, nxt[26], r;}st[maxn << 1];
int root, last, size;
void init(){
	root = last = size = 0;
	st[root].link = -1;
	st[root].len = 0;
}

void Extend(int c, int pos){
	int p = last, cur = ++ size;
	st[cur].len = st[p].len + 1;
	for(; ~p && st[p].nxt[c] == 0; p = st[p].link)
	    st[p].nxt[c] = cur;
	if(p == -1)
	    st[cur].link = root;
	else{
		int q = st[p].nxt[c];
		if(st[q].len == st[p].len + 1)
		    st[cur].link = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = st[cur].link = clone;
		}
	}
	st[cur].r = pos;
	last = cur;
}

typedef long long ll;
ll dp[maxn << 1];
int d[maxn << 1], w[maxn << 1];

char str[maxn];
int n;

int t[maxn << 2], lazy[maxn << 2];
#define lc id << 1
#define rc id << 1 | 1

void pushdown(int id, int l, int r){
	if(lazy[id] <= n){
        int mid = l + r >> 1;
		lazy[lc] = min(lazy[lc], lazy[id]);
		lazy[rc] = min(lazy[rc], lazy[id] - (mid - l + 1));
		lazy[id] = n << 1;
	}
}

void build(int id, int l, int r){
	t[id] = n;
	if(l == r){lazy[id] = n;return;}
	lazy[id] = n << 1;
	int mid = l + r >> 1;
	build(lc, l, mid);
	build(rc, mid + 1, r);
}

void update(int id, int l, int r, int x, int y, int val){
	if(l == x && r == y){
		lazy[id] = min(lazy[id], val);
		return;
	}
	pushdown(id, l, r);
	int mid = l + r >> 1;
	if(y <= mid)update(lc, l, mid, x, y, val);
	else if(x > mid)update(rc, mid + 1, r, x, y, val);
	else update(lc, l, mid, x, mid, val), update(rc, mid + 1, r, mid + 1, y, val - (mid - x + 1));
}

void update2(int id, int l, int r, int x, int y, int val){
    if(l == x && r == y){
		t[id] = min(t[id], val);
		return;
	}
	int mid = l + r >> 1;
	if(y <= mid)update2(lc, l, mid, x, y, val);
	else if(x > mid)update2(rc, mid + 1, r, x, y, val);
	else update2(lc, l, mid, x, mid, val), update2(rc, mid + 1, r, mid + 1, y, val);
}

void ask(int id, int l, int r){
	if(l == r){
		printf("%d\n", min(lazy[id], t[id]));
		return;
	}
	pushdown(id, l, r);
	t[lc] = min(t[lc], t[id]);
	t[rc] = min(t[rc], t[id]);
	int mid = l + r >> 1;
	ask(lc, l, mid);
	ask(rc, mid + 1, r);
}

int main(){
	init();
	scanf("%s", str + 1);
	n = strlen(str + 1);
	for(int i = 1; i <= n; i ++)
		Extend(str[i] - 'a', i);
	int cur = root;
	for(int i = 1; i <= n; i ++){
		cur = st[cur].nxt[str[i] - 'a'];
		dp[cur] = 1;
	}
	
	for(int i = 1; i <= size; i ++)w[st[i].len] ++;
	for(int i = 1; i <= size; i ++)w[i] += w[i - 1];
	for(int i = size; i >= 1; i --)d[w[st[i].len] --] = i;
	for(int i = size; i >= 1; i --)dp[st[d[i]].link] += dp[d[i]];
	build(1, 1, n);
	for(int i = 1; i <= size; i ++){
		if(dp[i] == 1){
			int l = st[i].r - st[i].len + 1, r = st[i].r - st[st[i].link].len;
			update(1, 1, n, l, r, st[i].len);
			if(r + 1 <= st[i].r)update2(1, 1, n, r + 1, st[i].r, st[st[i].link].len + 1);
		}
	}
	ask(1, 1, n);
	return 0;
}

  

最后對於多串,我們還有廣義后綴自動機~

具體可以見這篇博文的E題

題目還有[BZOJ 3926][ZJOI 2015]諸神眷顧的幻想鄉

[BZOJ 2780][SPOJ 8093] Sevenk Love Oimaster

題意:給定義一個字符串集合,再給定一些字符串,對於每個串詢問它在幾個串里出現過。
3 3
abcabcabc     --------字符串集合
aaa
aafe

abc                --------詢問字符串
a
ca
Sample Output
1 3 1
 
廣義后綴自動機:
對於多個串建立一個自動機
合並路徑
給每個節點打上一個屬於第幾個串的標記
對於parent樹上的一個節點
它要求的是它的子樹中有多少種不同的標記
離線詢問
 
對於每個值保留當前詢問的第一個出現的標記
具體請自行搜索HH的項鏈--

 

#include <bits/stdc++.h>
#define maxn 500000
using namespace std;

int n, m;

char s[maxn];

struct Edge_{int to, next;};
int In[maxn], Out[maxn], dfs_clock, dfn[maxn], ans[maxn];
vector<int>nxt[maxn];
int vis[maxn];
namespace BIT{
	int t[maxn];
	#define lowbit(i) i&(~i+1)
	void update(int pos, int val){
		if(!pos)return;
		for(int i = pos; i <= dfs_clock; i += lowbit(i))
		    t[i] += val;
	}
	
	int ask(int pos){
		if(!pos)return 0;
		int ret = 0;
		for(int i = pos; i; i -= lowbit(i))
		    ret += t[i];
		return ret;
	}
}

struct Edge{
	Edge_ edge[maxn];
	int h[maxn], cnt;
	void add(int u, int v){
		cnt ++;
		edge[cnt].to = v;
		edge[cnt].next = h[u];
		h[u] = cnt;
	}
	
	void dfs(int u){
		In[u] = ++ dfs_clock;
		dfn[dfs_clock] = u;
		for(int i = h[u]; i; i = edge[i].next)
		    dfs(edge[i].to);
		Out[u] = dfs_clock;
	}
	
	void solve(){
		for(int i = dfs_clock; i; i --){
			int now = dfn[i];
			for(int j = h[now]; j; j = edge[j].next){
				int v = edge[j].to;
				if(vis[v])nxt[i].push_back(vis[v]);
				vis[v] = i;
			}
		}
		for(int i = 1; i <= n; i ++)
			BIT::update(vis[i], 1);
	}
}A, B;

struct Node{
	int len, link;
	map<int, int>nxt;
}st[maxn];

int root, size, last;

void init(){
	root = size = last = 0;
	st[root].len = 0;
	st[root].link = -1;
}

void Extend(char ch, int Id){
	int c = ch - 'a', p = last, q = st[p].nxt[c];
	if(q){
		if(st[q].len == st[p].len + 1)
			last = q;
		else{
			int clone = ++ size;
			st[clone] = st[q];
			st[clone].len = st[p].len + 1;
			for(; ~p && st[p].nxt[c] == q; p = st[p].link)
			    st[p].nxt[c] = clone;
			st[q].link = clone;
			last = clone;
		}
	}
	else{
		int cur = ++ size;
		st[cur].len = st[p].len + 1;
		for(; ~p && !st[p].nxt[c]; p = st[p].link)
			st[p].nxt[c] = cur;
		if(p == -1)
			st[cur].link = root;
		else{
			q = st[p].nxt[c];
			if(st[q].len == st[p].len + 1)
			    st[cur].link = q;
			else{
				int clone = ++ size;
				st[clone] = st[q];
				st[clone].len = st[p].len + 1;
				for(; ~p && st[p].nxt[c] == q; p = st[p].link)
				    st[p].nxt[c] = clone;
				st[q].link = st[cur].link = clone;
			}
		}
		last = cur;
	}
	A.add(last, Id);
}

struct opt{
	int l, r, id;
	bool operator<(const opt& k)const{
		if(l != k.l)return l < k.l;
		return r < k.r;
	}
}q[maxn];

int main(){
	init();
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++){
		scanf("%s", s+1);
		int N = strlen(s+1);
		last = root;
		for(int j = 1; j <= N; j ++)
		    Extend(s[j], i);
	}
	
	for(int i = 1; i <= size; i ++)
		B.add(st[i].link, i);
	B.dfs(root);
	A.solve();
	
	int tot = 0;
	for(int i = 1; i <= m; i ++){
		scanf("%s", s+1);
		int N = strlen(s+1), now = root;
		bool flag = true;
		for(int j = 1; j <= N; j ++){
			int p = s[j] - 'a';
			if(!st[now].nxt[p]){
				flag = false;
				break;
			}
			now = st[now].nxt[p];
		}
		
		if(flag){
            ++ tot;
			q[tot].l = In[now];
			q[tot].r = Out[now];
			q[tot].id = i;
		}
	}

	sort(q+1, q+1+tot);
	int l = 1;
	for(int i = 1; i <= tot; i ++){
		while(l < q[i].l && l < dfs_clock){
			for(int j = 0; j < nxt[l].size(); j ++)
				BIT::update(nxt[l][j], 1);
			l ++;
		}
		ans[q[i].id] = BIT::ask(q[i].r) - BIT::ask(q[i].l-1);
	}
	for(int i = 1; i <= m; i ++)
		printf("%d\n", ans[i]);
	return 0;
}

 Sam終極boss:[BZOJ 3145][Feyat cup 1.5]Str

具體解題報告在這個博客中有


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM