题目
题目背景
“如果上帝是万能的,那么祂可以造出自己搬不起的石头吗?”
“可以。先造出 O U Y E \sf OUYE OUYE 。因为 O U Y E \sf OUYE OUYE 是全知全能的,所以 O U Y E \sf OUYE OUYE 会造出上帝无法搬起的石头。”
题目描述
给定字符串
S
S
S,将其所有后缀插入
t
r
i
e
\tt trie
trie 中,将终止节点涂成黑色。然后将出边数不为
1
1
1 的节点涂黑。最后把根节点涂黑。
现在,对于每两个黑色点 i , j ( dep i < dep j ) i,j\;(\text{dep}_i<\text{dep}_j) i,j(depi<depj),若 i i i 到 j j j 的路径上不存在其他黑点,则将路径上字符连起来形成字符串,将该字符串的本质不同子串数累加进答案。请输出答案。
数据范围与提示
∣
S
∣
⩽
1
0
6
|S|\leqslant 10^6
∣S∣⩽106,字符集大小
∣
Σ
∣
⩽
10
|\Sigma|\leqslant 10
∣Σ∣⩽10 。
思路
容易想到这个 t r i e \tt trie trie 就是反串的 SAM \textit{SAM} SAM 。于是立刻有了 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n) 做法。
可以优化,必定是因为询问区间有某种性质。可是我委实看不出。而且我也不知道出题人为什么能想到。
需建立正串的 SAM \textit{SAM} SAM 。那么,一个子串对应 t r i e \tt trie trie 上的黑点,当且仅当它所在的 SAM \textit{SAM} SAM 节点有至少两条出边,或其 endpos \text{endpos} endpos 集合包含 ∣ S ∣ |S| ∣S∣ 。而题目中的路径,就是正串 SAM \textit{SAM} SAM 沿着 d a g \rm dag dag 边走出的路径。
注意到白点的出边都是 1 1 1,因此,对于某个黑点 x x x,所有能到达 x x x 而不经过其他黑点的黑点,其实是一棵内向树。并且这些内向树是边不交的,因此树边的总数是 O ( n ) \mathcal O(n) O(n),即 SAM \textit{SAM} SAM 的 d a g \rm dag dag 边数。
当然,所有在 x x x 处结尾的路径,都对应 endpos ( x ) \text{endpos}(x) endpos(x) 为右端点的子串。所以我们只需找到最长的路径,暴力对这么长的字符串统计不同子串个数即可。注意起点为 x x x 时,它实际上对应 len ( x ) − len ( f a x ) \text{len}(x){-}\text{len}(fa_x) len(x)−len(fax) 个起点。
时间复杂度 O ( n ∣ Σ ∣ ) \mathcal O(n|\Sigma|) O(n∣Σ∣),即建立 SAM \textit{SAM} SAM 的复杂度。
代码
由于 MLE \textrm{MLE} MLE 的威胁,原题面中 ∣ Σ ∣ = 2 |\Sigma|=2 ∣Σ∣=2 。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype> // isdigit
using llong = long long;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
for(; isdigit(c); c=getchar()) a = a*10+(c^48);
return a*f;
}
const int MAXN = 1000006;
struct SFAM{ // Suffix Automaton
struct Node { int fa, ch[2], len, ep; };
Node node[MAXN<<1]; int cntNode, lst;
llong cnt_of_substr;
inline void clear(){
node[0].len = -1; // for convenience
cntNode = lst = 1, cnt_of_substr = 0;
node[1].ch[0] = node[1].ch[1] = 0;
}
void append(const int &c){
int p = lst, np = lst = ++ cntNode;
node[np].ch[0] = node[np].ch[1] = 0;
node[np].ep = node[np].len = node[p].len+1;
for(; p&&!node[p].ch[c]; p=node[p].fa) node[p].ch[c] = np;
cnt_of_substr += node[np].len-node[p].len-1;
if(!p){ node[np].fa = 1; return; }
int q = node[p].ch[c];
if(node[q].len == node[p].len+1)
return void(node[np].fa = q);
int nq = ++ cntNode; node[nq] = node[q];
node[nq].len = node[p].len+1;
node[q].fa = node[np].fa = nq;
for(; node[p].ch[c]==q; p=node[p].fa)
node[p].ch[c] = nq;
}
inline llong countSubstr(){
return cnt_of_substr;
}
int dfs(int, int, int[]); // declare; auxiliary for @a getAns
void getAns(); // declare
};
SFAM sam, tmp;
bool stop[MAXN<<1]; char str[MAXN];
struct Edge { int to, nxt; };
Edge e[MAXN<<2]; int head[MAXN<<1], cntEdge;
inline void addEdge(int a, int b){
e[cntEdge] = Edge{b,head[a]}, head[a] = cntEdge ++;
}
int SFAM::dfs(int o, int dis, int coe[]){
if(stop[o]){
coe[dis] += node[o].len-node[node[o].fa].len;
return dis; // maximum length
}
int res = dis; // won't be
for(int i=head[o]; ~i; i=e[i].nxt)
res = std::max(res,dfs(e[i].to,dis+1,coe));
return res;
}
int coe[MAXN];
void SFAM::getAns(){
for(int i=lst; i; i=node[i].fa) stop[i] = true;
memset(head+1,-1,cntNode<<2);
rep(i,1,cntNode){
rep(j,0,1) if(node[i].ch[j])
addEdge(node[i].ch[j],i);
if(node[i].ch[0] && node[i].ch[1])
stop[i] = true; // two outer edge
}
llong ans = 0;
rep(i,2,cntNode) if(stop[i]){
int len = 0; // maximum length
for(int j=head[i]; ~j; j=e[j].nxt)
len = std::max(len,dfs(e[j].to,1,coe));
tmp.clear();
for(int j=node[i].ep,k=1; k<=len; --j,++k){
tmp.append(str[j]^48);
ans += tmp.countSubstr()*coe[k];
coe[k] = 0; // clear meanwhile
}
}
printf("%lld\n",ans);
}
int main(){
scanf("%s",str+1);
int n = int(strlen(str+1));
sam.clear();
rep(i,1,n) sam.append(str[i]^48);
sam.getAns();
return 0;
}