[ACNOI2022]正反字符串

38 篇文章 0 订阅
13 篇文章 0 订阅

题目

题目背景
“如果上帝是万能的,那么祂可以造出自己搬不起的石头吗?”

“可以。先造出 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 S106,字符集大小 ∣ Σ ∣ ⩽ 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值