NOI2014-动物园 题解

P2375 非常棒的 KMP 水题,感觉自己的思路比题解区靠前的绝大部分题解都简单,在洛谷远古题新题解的曝光量不高,所以在这写这篇题解。

题意

多组数据,给定字符串 S,求对于 S 的每一个前缀 S_{1\cdots i},它的长度 \le \frac{i}{2} 的 border 前缀数量 \text{num}_i 你需要输出 \prod(\text{num}_i+1),对 10^9+7 取模。

T\le5,\vert S\vert\le10^6

思路
这是我的 KMP 预处理板子,后面的代码看不懂的时候可以和这个对比一下:

for(int i = 1,j = 0;i < m;i ++){//每一轮给 S_{1...i+1} 找最长的 border
	while(j && s[i + 1] != s[j + 1]) j = nxt[j];//下一个匹配不上,回跳
	if(s[i + 1] == s[j + 1]) j ++;//匹配上了 +1
    nxt[i + 1] = j;
}

我们用 \text{nxt}_i 表示 S_{1\cdots i} 的最长 border。

考虑刨去长度限制,能不能求 S 的每个前缀的 border 数量 \text{cnt}_i (不包括这个前缀本身)?

思考两分钟后还想不到再打开,这一步非常简单

如果 nxt_i 为零,cnt_i=0;否则,S_{1\cdots \text{nxt}_i}S_{1\cdots i} 的一个 border,前者的 border 就是后者的其他 border,所以 \text{cnt}_i=\text{cnt}_{\text{nxt}_i}+1

于是我们线性求出了 \text{nxt} 数组和 \text{cnt} 数组。代码:

for(int i = 1,j = 0;i < m;i ++){
	while(j && s[i + 1] != s[j + 1]) j = nxt[j];
	if(s[i + 1] == s[j + 1]) j ++;
	nxt[i + 1] = j;
    cnt[i + 1] = j ? cnt[j] + 1 : 0;//比板子就多了这一行
}

然后令我们要求的数组为 \text{num}
根据求 \text{cnt} 的思想,我们只需要找到 S_{1\cdots i} 的最长的长度 \le\frac{i}{2} 的前缀 S_{1\cdots j}\text{num}_i 就是 \text{cnt}_j+1 (这个 border 和这个 border 的所有 border)。

怎么找 S 所有前缀的最长的不长于此前缀一半的 border 呢?

也非常简单,思考两分钟

观察 KMP 的过程,S_{1\cdots i} 的最长 border 一定来自于一个 S_{1\cdots i-1} 的 border(“来自于”即在后者的后面加上 S_i 获得)。

再跑一遍 KMP,加上限制 2(j+1)\le i,不满足就得往回跳 j=nxt[j] 即可。

感性理解:很显然是正确的,不让 border 的长度到一半就可以了。

正确性:S_{1\cdots i} 的满足条件的最长 border S_{1\cdots j},有 2j\le i,是由 S_{i\cdots i-1} 的 border S_{1\cdots j-1} 推导过来的,显然 2j\le i\rightarrow 2(j-1)\le i-1,所以这个 S_{1\cdots j-1} 是一个 S_{1\cdots i-1} 的合法的 border,不合法 border($2(j-1)>i-1$)是一定不会影响后面推导的。

因此,强行添加限制排除所有的不合法 border 并不会影响正确性。

高效性:时间复杂度同 KMP,证明同 KMP。

此部分代码:

for(int i = 1,j = 0;i < m;i ++){//式子不一样是因为我的 KMP 实质上是在匹配 i+1 和 j+1, 
	while(j && (s[i + 1] != s[j + 1] || 2 * j >= i)) j = nxt[j]; //2j<=i 就变成了 2(j+1)<=i+1, 
	if(s[i + 1] == s[j + 1] && 2 * j < i) j ++;//化简后就是 2j<i。 
	num[i] = j ? cnt[j] + 1 : 0;
}

代码

多组数据{
    read_string s[1...n]
    for(int i = 1,j = 0;i < n;i ++){//KMP 板子,额外统计 border 数量 
	    while(j && s[i + 1] != s[j + 1]) j = nxt[j];
	    if(s[i + 1] == s[j + 1]) j ++;
	    nxt[i + 1] = j,cnt[i + 1] = cnt[j] + (j > 0);
    }
	for(int i = 1,j = 0;i < n;i ++){ 
		while(j && (s[i + 1] != s[j + 1] || 2 * j >= i)) j = nxt[j];//加上限制后回跳   
		if(s[i + 1] == s[j + 1] && 2 * j < i) j ++;//可以匹配 
		ans = ans * (cnt[j] + (j > 0) + 1) % MOD;//题目所求为 (num[i]+1) 的积 
	}
	printf("%lld\n",ans);
}
4 个赞

求赞 QwQ

2 个赞

太强啦%%%但是不能发 AC 代码 qwq

1 个赞

太强啦%%%但是不能发 AC 代码 qwq

1 个赞

没问题啊,抄他的代码就等于抄题解了

@stringdp100005 题目题解里没有他写的呀

1 个赞

不能发 AC 代码吗?加个防伪行不行 或者只放关键部分

1 个赞

你可以把所有分号都变成中文()

@chenxi1 可以的,发个伪代码

现在不能 AC 了 :grinning_face_with_big_eyes: