Pluto_Rabbit's Blog

蒟蒻妹纸一枚,轻拍

【学习】字符串的各种-SAM

Jacinth posted @ 2016年9月11日 16:45 in Lerning with tags 字符串 学习 后缀自动机 Learning , 334 阅读

想来SAM也真是太神辣

感觉跳坑里就死在里面出不来了

所以把它拎出来再学(bian)习(shi)一下

后缀自动机可以被理解为所有子串的简明信息。后缀自动机以压缩后的形式包含了一个长度为n的字符串的所有信息,仅需要O(n)的空间。并且,它能在O(n)时间内被构造。(人家都O(n)了没法无视啊!!!)

SAM 是处理字符串相关问题的有力工具, 可以解决包括但不限于以下问题:

  • 判断W是否为T的子串
  • 判断W是否为T的后缀
  • 计算T的不同子串个数
  • 计算T的不同子串的总长度
  • 计算W在T中的出现次数
  • 查找W在T中第一次出现位置
  • 查找W在T中所有的出现位置
  • 查找T1&T2的最长公共子串
  • 查找T1到Tk的最长公共子串
  • 查找T的最小循环节
  • 查找T的第k小子串
  • 查找最短的不是T的子串的字符串
  • ......

 

STATE

我们定义$endpos(T,s)$为T中s出现位置终点的集合。且当$endpos(T,s_1) == endpos(T,s_2)$ 我们就称$s_1$和$s_2$终点等价
我们可以知道,某个状态u,可以识别T的某个子串的集合, 设这个集合为$substrings(u)$ 而$substrings(u)$中的两个子串都是终点等价的。
考虑对于T的任意两个子串$s_1,s_2$而言,$endpos(T,s_1),endpos(T,s_2)$之间的关系(假设$length(s_1) \leq length(s_2)$):
若$endpos(s_1) \cap endpos(s_2) \neq \emptyset$,即$s_1,s_2$同时以T的某个字符为结尾,那么$s_1$必为$s_2$的后缀(含$s_1==s_2$)
同时,若$s_1$是$s_2$的后缀,那么$endpos(s_1) \supseteq endpos(s_2)$(子串长度越长队endpos的限制越多导致endpos的集合越小),即$endpos(s_1) \cap endpos(s_2) \neq \emptyset$。
所以 $endpos(s_1) \supseteq endpos(s_2) \Leftrightarrow s_1 \sqsupset s_2$
因而我们只剩下了两种状况

$$ \begin{cases} endpos(s_1) \supseteq endpos(s_2), & \text{if } s_1 \sqsupset s_2 \\
endpos(s_1) \cap endpos(s_2) = \emptyset, & \text{otherwise}
\end{cases} $$

我们发现由于同一$substrings(u)$下最长子串和最短子串间的endpos相等,则最短子串为最长子串的后缀,对于所有$substrings(u)$下的$s_i$我们可以发现它都是$substrings(u)$的后缀,同样的也都是最长子串的后缀。
所以这就意味这每一个状态都对应一段连续的后缀,(很厉害的样子)

根据之前的内容,对于一个状态u我们可以给出:

  • $longest(u) : substring(u)$中最长的子串
  • $shortest(u): substring(u)$中最短的子串
  • $maxlen(u): longest(u)$的长度
  • $minlen(u): shortest(u)$的长度

而一个状态u可以由以上四个来表示。

 

SUFFIX LINK

我们给定两个状态$u,v$ 那么 $v = link(u)$即为$u$到$v$的suffix link 且 $maxlen(v) + 1 = minlen(u)$. suffix link 可以把状态之间用suffix link 连起来

 

STATE'S TRANSITIONS

我们给定一个状态$u$和标记$c$,$trans(u,c) = v$表示状态$u$到状态$v$存在状态转移路径。

我们可以知道以$v$为终点的转移都有一个相同的标记。

令$U_v = \{ u_i \mid trans(u_i, c) = v \}$

我们之前知道对于一个状态$v,substring(v)$中的子串为连续的,又由于状态$v$的转移都有相同的符号,那么$\{ s_i \mid s_i \in substrings(u_i), u_i \in U_v \}$也必为连续的。

 

SAM的构造步骤

我们对于SAM中的每个状态,维护以下内容

  • $maxlen(u)$
  • $link(u)$
  • $trans(u,\Sigma)$[以$u$为起点的状态转移]

构造过程:

  • 新建状态$np$,使$maxlen(np) = maxlen(last) + 1$
  • 从状态$last$开始沿着suffix-link path访问每个状态,直到$p = null$或存在$trans(p,T_i)$,令$trans(p,T_i) = np$
  • 判断$p$是否为$null$,
    • 若$p = null$,则$link(np) = st, la = np$ 返回第一步
    • 否则$p$为某个存在的状态且$trans(p,T_i) = q$
      • 若$maxlen(p)+1 = maxlen(q)$ 则 $link(np) = q, la = np$ 返回第一步
      • 若$maxlen(p)+1 < maxlen(q)$ 则从$q$状态拆出状态$nq$,令$$link(nq) = link(q) , link(q) = link(np) = nq, la = np$$ 返回第一步

萌萌哒的extend代码QAQ:

void extend(int xx) //mx(p)表示maxlen(p), a[p][xx]表示trans(p,xx), fa[p]表示link(p)
{
	int p = la,np = la = ++tot;
	mx[np] = mx[p]+1;
	while (!a[p][xx] && p) a[p][xx] = np, p = fa[p];
	if (!p) fa[np] = 1;
	else
	{
		int q = a[p][xx];
		if (mx[p]+1 == mx[q]) fa[np] = q;
		else
		{
			int nq = ++tot;
			mx[nq] = mx[p]+1;
			memcpy(a[nq],a[q],sizeof(a[q]));
			fa[nq] = fa[q], fa[np] = fa[q] = nq;
			while (a[p][xx] == q) a[p][xx] = nq, p = fa[p];
		}
	}
}

 

其他信息的维护

  • $minlen(u)$
    • 有$v = link(u) \implies maxlen(v) + 1 = minlen(u)$ 直接在更新$link(u)$时更新$minlen(u)$就可以了
  • $first\_endpos(u)$ 表示 $endpos(u)$中最小的下标
    • 对于状态$u$, $endpos(u)$的变更伴随着指向状态$u$的suffix-link的建立。所以我们首先维护$first\_endpos(u)$,在后续的过程中,我们只用沿着suffix-link反向DFS可得$endpos(u)$
    • 对于$first\_endpos(u)$的维护,我们只用
      • 在新建状态$np$时,设$first\_endpos(np)=i$
      • 在新建状态$nq$时,设$$first\_endpos(nq)=first\_endpos(q)$$
  • $accept(u)$ 表示接收节点
    • 我们只用看$u$是否在从$la$到$st$的suffix path上即可。因此我们可以在创建SAM后扫一遍suffix path就可以得到所有的接收状态

似乎to be continued...

 

各种各样的参考资料:

先搬了几道题(无视奇奇怪怪的时间线吧qaq)

BZOJ-3998 

Descriprtion 对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input

第一行是一个仅由小写英文字母构成的字符串S

第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。

Output 输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input

aabc

0 3

Sample Output aab
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#define NN 500050
#define MM 1000010
using namespace std;
int n,t,k;
char ch[NN];
struct SAM
{
	int tot,la,a[MM][30],mx[MM],fa[MM],v[NN],q[MM],val[MM],sum[MM];
	SAM(){ la=++tot;}
	void extend(int xx)
	{
		int p=la,np=la=++tot;
		mx[np]=mx[p]+1,val[np]=1;
		while (!a[p][xx] && p) a[p][xx]=np,p=fa[p];
		if (!p) fa[np]=1;
		else
		{
			int q=a[p][xx];
			if (mx[p]+1==mx[q]) fa[np]=q;
			else
			{
				int nq=++tot; mx[nq]=mx[p]+1;
				memcpy(a[nq],a[q],sizeof(a[q]));
				fa[nq]=fa[q],fa[np]=fa[q]=nq;
				while (a[p][xx]==q) a[p][xx]=nq,p=fa[p];
			}
		}
	}
	void pre()
	{
		for (int i=1;i<=tot;++i) v[mx[i]]++;
		for (int i=1;i<=n;++i) v[i]+=v[i-1];
		for (int i=tot;i;i--) q[v[mx[i]]--]=i;
		for (int i=tot;i;i--)
		{
			int tmp=q[i];
			if (t==1) val[fa[tmp]]+=val[tmp]; else val[tmp]=1;
		}
		val[1]=0;
		for (int i=tot;i;i--)
		{
			int tmp=q[i]; sum[tmp]=val[tmp];
			for (int j=0;j<26;++j) sum[tmp]+=sum[a[tmp][j]];
		}
	}
	void dfs(int xx,int yy)
	{
		if (yy<=val[xx]) return;
		yy-=val[xx];
		for (int i=0;i<26;++i)
			if (int tmp=a[xx][i])
			{
				if(yy<=sum[tmp])
				{
					putchar(i+'a'); dfs(tmp,yy); return;
				}
				yy-=sum[tmp];
			}
	}
}SAM;

int main()
{
	scanf("%s",ch+1); n=strlen(ch+1);
	scanf("%d%d",&t,&k);
	for (int i=1;i<=n;++i) SAM.extend(ch[i]-'a');
	SAM.pre();
	if (k>=SAM.sum[1]) { puts("-1"); return 0;}
	else SAM.dfs(1,k);
	return 0;
}

BZOJ-4516: [Sdoi2016]生成魔咒

Descriprtion
求每次操作后不同子串个数

听说有SA的做法飞起来

但是由于在学习SAM就用SAM了呀(明明SAM简单辣

其实更新的时候加上$len[i]-len[fa[i]]$就可以了(板子里是$mx[i]-mx[fa[i]]$)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MM = 200010;
int n,x;
LL ans;
struct Sam
{
	int tot,la,mx[MM],fa[MM],val[MM];
	map <int,int> a[MM];
	Sam() { la = ++tot; }
	void extend(int xx)
	{
		int p = la, np = ++tot; la = np;
		mx[np] = mx[p] + 1, val[np] = 1;
		while (!a[p][xx] && p) a[p][xx] = np, p = fa[p];
		if (!p) fa[np] = 1;
		else
		{
			int q = a[p][xx];
			if (mx[p]+1 == mx[q]) fa[np] = q;
			else
			{
				int nq = ++tot;
				mx[nq] = mx[p]+1;
				a[nq] = a[q];
				fa[nq] = fa[q] , fa[np] = fa[q] = nq;
				while (a[p][xx] == q && p) a[p][xx] = nq, p = fa[p];
			}
		}
		ans += mx[np] - mx[fa[np]];
		printf("%lld\n",ans);
	}
}SAM;

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;++i) scanf("%d",&x), SAM.extend(x);
	return 0;
}

BZOJ-4566: [Haoi2016]找相同字符

Descriprtion
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
Input
两行,两个字符串$s_1,s_2$,长度分别为$n_1,n_2$。$1 \leq n_1, n_2 \leq 200000$,字符串中只有小写字母
Output
输出一个整数表示答案
Sample Input
aabb
bbaa
Sample Output
10

每个节点对答案的贡献为 $(mx[x]-mx[fa[x]]) \times sz[0][x] \times sz[1][x]$

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int NN = 200005,MM = 800005;
int mx[MM],a[MM][26],fa[MM],tot=0,pos,la,q[MM],t[MM],sz[2][MM],n1,n2;
LL ans;
char ch1[NN],ch2[NN];
 
inline void extend(int xx)
{
	int p = la, np = la = ++tot;
	mx[np] = mx[p] + 1;
	while (!a[p][xx] && p) a[p][xx] = np, p = fa[p];
	if (!p) fa[np] = 1;
	else
	{
		int q = a[p][xx];
		if (mx[p]+1 == mx[q]) fa[np] = q;
		else
		{
			int nq = ++tot;
			mx[nq] = mx[p] + 1;
			memcpy(a[nq],a[q],sizeof(a[nq]));
			fa[nq] = fa[q] , fa[np] = fa[q] = nq;
			while (a[p][xx] == q && p) a[p][xx] = nq, p = fa[p];
		}
	}
}
int main()
{
	scanf("%s",ch1); n1 = strlen(ch1);
	scanf("%s",ch2); n2 = strlen(ch2);
	la = ++tot;
	int p=1;
	for (int i=0;i<n1;++i)
	{
		extend(ch1[i]-'a');
		p=a[p][ch1[i]-'a']; sz[0][p]++;
	}
	p = la = 1;
	for (int i=0;i<n2;++i)
	{
		extend(ch2[i]-'a');
		p=a[p][ch2[i]-'a']; sz[1][p]++;
	}
	ans = 0ll;
	for (int i=1;i<=tot;++i) ++t[mx[i]];
	for (int i=1;i<=tot;++i) t[i] += t[i-1];
	for (int i=1;i<=tot;++i) q[t[mx[i]]--] = i;
	for (int i=tot;i;--i) sz[0][fa[q[i]]] += sz[0][q[i]], sz[1][fa[q[i]]] += sz[1][q[i]];
	for (int i=1;i<=tot;++i) ans += (LL)(mx[i] - mx[fa[i]]) * sz[0][i] * sz[1][i];
	printf("%lld\n",ans);
	return 0;
}

HDU5343 MZL'S Circle Zhou

Descriprtion
给定两个字符串A,B,分别截取其中的子串X和Y,连接组成X+Y。
求不同的X+Y的方案数。

分别对$A$,$B$建立后缀自动机

对$A$进行查找$X$,当$X$的末尾不能接上字符$i$,那么就到$B$中查找所有以i为开头的子串

在两个DAG上DP就可以了【撒花

(答案用unsigned long long存啊啊啊啊,傻逼的我一直死在这里不知道多少次了

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const int NN = 200010;
char ch1[NN],ch2[NN];
LL dp1[NN],dp2[NN];
int T;

struct SAM
{
	int a[NN][26],mx[NN],la,tot,fa[NN];
	void Clr()
	{
		la = tot = 0;
		fa[0] = -1;
		memset(a,-1,sizeof(a));
		mx[0] = 0;
	}
	void extend(int xx)
	{
		int p = la, np = la = ++tot;
		mx[np] = mx[p] + 1;
		while (a[p][xx]==-1 && (~p)) a[p][xx] = np, p = fa[p];
		if (p == -1) fa[np] = 0;
		else
		{
			int q = a[p][xx];
			if (mx[p]+1 == mx[q]) fa[np] = q;
			else
			{
				int nq = ++tot;
				mx[nq] = mx[p] + 1;
				memcpy(a[nq],a[q],sizeof(a[nq]));
				fa[nq] = fa[q] , fa[np] = fa[q] = nq;
				while (a[p][xx] == q && (~p)) a[p][xx] = nq, p = fa[p];
			}
		}
	}
}A,B;

LL DFS2(int xx)
{
	if (xx == -1) return 0;
	LL &pre = dp2[xx];
	if (pre != -1) return pre;
	pre = 1;
	for (int i=0;i<26;++i) pre += DFS2(B.a[xx][i]);
	return pre;
}

LL DFS1(int xx)
{
	LL &pre = dp1[xx];
	if (pre != -1) return pre;
	pre = 1;
	for (int i=0;i<26;++i)
	{
		if (~A.a[xx][i]) pre += DFS1(A.a[xx][i]);
		else pre += DFS2(B.a[0][i]);
	}
	return pre;
}
int main()
{
	scanf("%d",&T);
	for (int _=1;_<=T;++_)
	{
		A.Clr(), B.Clr();
		scanf("%s%s",ch1,ch2);
		int l1 = strlen(ch1), l2 = strlen(ch2);
		for (int i=0;i<l1;++i) A.extend(ch1[i]-'a');
		for (int i=0;i<l2;++i) B.extend(ch2[i]-'a');
		memset(dp1,-1,sizeof(dp1)), memset(dp2,-1,sizeof(dp2));
		printf("%llu\n",DFS1(0));
	}
	return 0;
}

BZOJ-4032:[HEOI2015]最短不公共子串

Descriprtion
给两个小写字母串A,B,请你计算:
(1) A的一个最短的子串,它不是B的子串
(2) A的一个最短的子串,它不是B的子序列
(3) A的一个最短的子序列,它不是B的子串
(4) A的一个最短的子序列,它不是B的子序列
Input

两行,小写字母表示的字符串A和B

Output 输出4行,每行一个整数,表示以上4个问题的答案的长度。如果没有符合要求的答案,输出-1.
Sample Input
aabbcc
abcabc

 

Sample Output
2
4
2
4

四个小问,答案都是最小公共子××+1

感觉非常厉害

对于子串,在建立的后缀自动机上跑匹配

对于子序列,采用DP的贪心来做,la[i][j]表示第i位后第一个j出现的位置

然后用BFS四(fu)合(zhi)一就可以了

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

const int NN = 2001, MM = 4001;
char ch1[NN],ch2[NN];
int l1,l2,la1[NN][26],la2[NN][26],vis[NN][MM],a[MM][26],fa[MM],la=1,tot=1,mx[MM];
struct REC
{ int x,y,l;}tmp,tmpp;
queue <REC> que;

void extend(int xx)
{
	int p = la,np = la = ++tot;
	mx[np] = mx[p]+1;
	while (!a[p][xx] && p) a[p][xx] = np, p = fa[p];
	if (!p) fa[np] = 1;
	else
	{
		int q = a[p][xx];
		if (mx[p]+1 == mx[q]) fa[np] = q;
		else
		{
			int nq = ++tot;
			mx[nq] = mx[p]+1;
			memcpy(a[nq],a[q],sizeof(a[q]));
			fa[nq] = fa[q], fa[np] = fa[q] = nq;
			while (a[p][xx] == q) a[p][xx] = nq, p = fa[p];
		}
	}
}
void Clr()
{
	memset(vis,0,sizeof(vis));
	while (!que.empty()) que.pop();
}
void Solve1()
{
	Clr();
	for (int i=0;i<l1;++i) tmp.x = i, tmp.y = 1, tmp.l = 0, que.push(tmp);
	while (!que.empty())
	{
		tmp = que.front(); que.pop();
		if (tmp.x == l1) continue;
		int nx = tmp.x +1, ny = a[tmp.y][ch1[nx]-'a'];
		if (ny == 0) { printf("%d\n",tmp.l+1); return; }
		if (!vis[nx][ny]) vis[nx][ny] = 1, tmpp.x = nx, tmpp.y = ny, tmpp.l = tmp.l+1, que.push(tmpp);
	}
	printf("%d\n",-1); return;
}
void Solve2()
{
	Clr();
	for (int i=0;i<l1;++i) tmp.x = i, tmp.y = 0, tmp.l = 0, que.push(tmp);
	while (!que.empty())
	{
		tmp = que.front(); que.pop();
		if (tmp.x == l1) continue;
		int nx = tmp.x+1, ny = la2[tmp.y][ch1[nx]-'a'];
		if (ny == 0x3f3f3f3f) { printf("%d\n",tmp.l+1); return;}
		if (!vis[nx][ny]) vis[nx][ny] = 1, tmpp.x = nx, tmpp.y = ny, tmpp.l = tmp.l+1, que.push(tmpp);
	}
	printf("%d\n",-1); return;
}
void Solve3()
{
	Clr(); tmp.x = 0, tmp.y = 1, tmp.l = 0, que.push(tmp);
	while (!que.empty())
	{
		tmp = que.front(); que.pop();
		for (int i=0;i<26;++i)
		{
			int nx = la1[tmp.x][i];
			if (nx == 0x3f3f3f3f) continue;
			int ny = a[tmp.y][ch1[nx]-'a'];
			if (ny == 0) { printf("%d\n",tmp.l+1); return;}
			if (!vis[nx][ny]) vis[nx][ny] = 1, tmpp.x = nx, tmpp.y = ny, tmpp.l = tmp.l+1, que.push(tmpp);
		}
	}
	printf("%d\n",-1); return;
}
void Solve4()
{
	Clr(); tmp.x = 0, tmp.y = 0, tmp.l = 0, que.push(tmp);
	while (!que.empty())
	{
		tmp = que.front(); que.pop();
		for (int i=0;i<26;++i)
		{
			int nx = la1[tmp.x][i], ny = la2[tmp.y][i];
			if (nx == 0x3f3f3f3f) continue;
			if (ny == 0x3f3f3f3f) { printf("%d\n",tmp.l+1); return;}
			if (!vis[nx][ny]) vis[nx][ny] = 1, tmpp.x = nx, tmpp.y = ny, tmpp.l = tmp.l+1, que.push(tmpp);
		}
	}
	printf("%d\n",-1); return;
}
int main()
{
	scanf("%s%s",ch1+1,ch2+1);
	l1 = strlen(ch1+1), l2 = strlen(ch2+1);
	for (int i=1;i<=l2;++i) extend(ch2[i]-'a');
	memset(la1,0x3f,sizeof(la1)), memset(la2,0x3f,sizeof(la2));
	for (int i=0;i<=l1;++i)
		for (int j=i+1;j<=l1;++j)
			la1[i][ch1[j]-'a'] = min(j,la1[i][ch1[j]-'a']);
	for (int i=0;i<=l2;++i)
		for (int j=i+1;j<=l2;++j)
			la2[i][ch2[j]-'a'] = min(j,la2[i][ch2[j]-'a']);
	Solve1(), Solve2(), Solve3(), Solve4();
	return 0;
}

BZOJ-3238: [Ahoi2013]差异

我们需要一个后缀树,然后两个后缀的lcp就是它们lca的len,我们在后缀树上进行DP就可以了。

后缀树呢我们可以通过反序后缀自动机得到。

#include <bits/stdc++.h>
using namespace std;
const int NN = 1000010;
typedef long long LL;
int val[NN],fa[NN],a[NN][27],mx[NN],tot=1,cnt,head[NN],la=1,len;
LL ans;
struct E
{
	int ne,v;
}e[NN];
inline void extend(int xx)
{
	int p = la, np = la = ++tot;
	mx[np] = mx[p] + 1, val[np] = 1;
	while (!a[p][xx] && p) a[p][xx] = np, p = fa[p];
	if (!p) fa[np] = 1;
	else
	{
		int q = a[p][xx];
		if (mx[p]+1 == mx[q]) fa[np] = q;
		else
		{
			int nq = ++tot;
			mx[nq] = mx[p] + 1;
			memcpy(a[nq],a[q],sizeof(a[nq]));
			fa[nq] = fa[q] , fa[np] = fa[q] = nq;
			while (a[p][xx] == q && p) a[p][xx] = nq, p = fa[p];
		}
	}
}
void Build(int xx,int yy)
{
	e[++cnt].ne = head[xx], head[xx] = cnt, e[cnt].v = yy;
}

void DP(int xx,int p)
{
	for (int i=head[xx];i;i=e[i].ne)
	{
		DP(e[i].v,xx);
		val[xx] += val[e[i].v];
	}
	mx[xx] -= mx[p];
	ans -= (LL)val[xx] * (val[xx]-1) * mx[xx];
}
char ch[NN];
int main()
{
	scanf("%s",ch);
	len = strlen(ch);
	for (int i=len-1;i>=0;--i) extend(ch[i]-'a');
	ans = (LL) (len-1) * len * (len+1) >> 1ll;
	for (int i=2;i<=tot;++i) Build(fa[i],i);
	for (int i=head[1];i;i=e[i].ne) DP(e[i].v,1);
	printf("%lld\n",ans);
	return 0;
}

BZOJ-3926

神奇的题目描述啊。。。感觉语文不好的后果来了

ZJOI的题目呢。。。马上省选了好虚。。强省蒟蒻滚粗无疑辣。。

#include <iostream> 
#include <cstdio> 
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#define LL long long
#define MM 4000005
#define NN 100005
using namespace std;
LL ans;
int n,c,cc[NN],d[NN],tot=0,head[NN];
struct Edge
{
	int ne,v;
}e[NN<<1];
void Build(int xx,int yy)
{
	e[++tot].ne=head[xx], head[xx]=tot, e[tot].v=yy;
	e[++tot].ne=head[yy], head[yy]=tot, e[tot].v=xx;
}
struct SAM
{
	int cnt,fa[MM],a[MM][10],mx[MM];
	SAM(){cnt=1;}
	int extend(int p,int c)
	{
		int np=++cnt; mx[np]=mx[p]+1;
		while (!a[p][c] && p) a[p][c]=np, p=fa[p];
		if (!p) fa[np]=1;
		else
		{
			int q=a[p][c];
			if (mx[p]+1==mx[q]) fa[np]=q;
			else
			{
				int nq=++cnt;mx[nq]=mx[p]+1;
				memcpy(a[nq],a[q],sizeof(a[q]));
				fa[nq]=fa[q], fa[np]=fa[q]=nq;
				while (a[p][c]==q) a[p][c]=nq, p=fa[p];
			}
		}
		return np;
	}
	void solve()
	{
		for (int i=1;i<=cnt;++i) ans+=mx[i]-mx[fa[i]];
	}
}Sam;
void DFS(int x,int fa,int p)
{
	int t=Sam.extend(p,cc[x]);
	for (int i=head[x];i;i=e[i].ne)
		if (e[i].v!=fa)
			DFS(e[i].v,x,t);
}
int main()
{
	scanf("%d%d",&n,&c);
	for (int i=1;i<=n;++i) scanf("%d",&cc[i]);
	int x,y;
	for (int i=1;i<n;++i)
	{
		scanf("%d%d",&x,&y);
		Build(x,y);
		d[x]++, d[y]++;
	}
	for (int i=1;i<=n;++i)
		if (d[i]==1) DFS(i,0,1);
	Sam.solve();
	printf("%lld\n",ans);
	return 0;
}

BZOJ-4199

唔。。2015年的NOI题目呢。。。别忘了考虑负数。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#define LL long long
#define NN 600006
#define INF 2e18
using namespace std;
LL mav[NN],ma1[NN],ma2[NN],mi1[NN],mi2[NN],head[NN],n,m,a[NN],size[NN],ans[NN],tot=0;
bool vis[NN];
char ch[NN];
struct Edge
{
	int v,ne,w;
}e[NN];
void Build(LL xx,LL yy,LL zz)
{
	e[++tot].ne=head[xx], head[xx]=tot, e[tot].v=yy, e[tot].w=zz;
}
struct SAM
{
	LL cnt,la,ne[NN][26],fa[NN],mx[NN],t[NN],now,tmp,nx[NN];
	SAM(){la=++cnt;}
	void extend(int xx)
	{
		LL p=la,np=la=++cnt,c=ch[xx]-'a'; 
		mx[np]=mx[p]+1, t[np]=xx;
		while (!ne[p][c] && p) ne[p][c]=np, p=fa[p];
		if (!p) fa[np]=1;
		else
		{
			LL q=ne[p][c];
			if (mx[p]+1==mx[q]) fa[np]=q;
			else
			{
				LL nq=++cnt; mx[nq]=mx[p]+1;
				memcpy(ne[nq],ne[q],sizeof(ne[q]));
				fa[nq]=fa[q], fa[np]=fa[q]=nq;
				while (ne[p][c]==q) ne[p][c]=nq, p=fa[p];
			}
		}
	}
	void DFS(LL xx,LL h)
	{
		if (t[xx]) ma1[xx]=mi1[xx]=a[t[xx]], size[xx]=1;
			else ma1[xx]=-INF,mi1[xx]=INF, size[xx]=0;
		ma2[xx]=-INF, mi2[xx]=INF;
		for (LL i=head[xx];i;i=e[i].ne)
		{
			LL yy=e[i].v;
			DFS(yy,h+e[i].w);
			if (ma1[yy]>ma1[xx])
			{
				ma2[xx]=ma1[xx], ma1[xx]=ma1[yy];
				if (ma2[yy]>ma2[xx]) ma2[xx]=ma2[yy];
			}
			else
			if (ma1[yy]>ma2[xx]) ma2[xx]=ma1[yy];
			if (mi1[yy]<mi1[xx])
			{
				mi2[xx]=mi1[xx], mi1[xx]=mi1[yy];
				if (mi2[yy]<mi2[xx]) mi2[xx]=mi2[yy];
			}
			else
			if (mi1[yy]<mi2[xx]) mi2[xx]=mi1[yy];
			size[xx]+=size[yy], nx[xx]+=nx[yy];
		}
		now=-INF; tmp=(size[xx]*(size[xx]-1))/2;
		if (size[xx]>=4)now=max(max(ma1[xx]*ma2[xx],mi1[xx]*mi2[xx]),max(ma1[xx]*mi2[xx],ma2[xx]*mi1[xx]));
		else
		if (size[xx]>=3) now=max(max(ma1[xx]*ma2[xx],mi1[xx]*mi2[xx]),ma1[xx]*mi1[xx]);
		else
		if (size[xx]>=2) now=ma1[xx]*ma2[xx];
		if (now!=INF) ans[h]+=tmp-nx[xx], mav[h]=max(mav[h],now);
		nx[xx]+=tmp-nx[xx];
	}
}Sam;
int main()
{
	scanf("%lld",&n);
	scanf("%s",ch+1);
	for (LL i=n;i;i--) Sam.extend(i);
	vis[1]=1;
	for (LL i=1;i<=Sam.cnt;++i)
		if (Sam.t[i]&&!vis[i])
			for (LL j=i;!vis[j];vis[j]=1,j=Sam.fa[j]) Build(Sam.fa[j],j,Sam.mx[j]-Sam.mx[Sam.fa[j]]);
	for (LL i=0;i<=n;++i) mav[i]=-INF;
	for (LL i=1;i<=n;++i) scanf("%lld",&a[i]);
	Sam.DFS(1,0);
	for (LL i=n-1;i>=0;--i) ans[i]=ans[i]+ans[i+1], mav[i]=max(mav[i],mav[i+1]);
	for (LL i=0;i<n;++i)
	{
		if (mav[i]==-INF) mav[i]=0;
		printf("%lld %lld\n",ans[i],mav[i]);
	}
	return 0;
}

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter