2020NOIP-B-字符串匹配(哈希,思维)

博客探讨如何使用哈希技巧解决NOIP中涉及字符串匹配的难题,通过预处理哈希值快速查找循环节,同时处理奇偶限制,分析枚举AB、确定A和B的分割点以及优化超时问题。代码实现虽可能有稳定性问题,但所体现的思考过程值得学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

分析

题目要求原字符串可以分解成多少种 ( A B ) i C (AB)^iC (AB)iC的形式,该式子的意义见题面。

如果不考虑那个奇怪的奇偶限制,那么应该怎么做?

首先有两种想法,肯定是先枚举一部分,然后通过一些奇怪的优化来解决。
比较方便的做法,就是枚举 A B AB AB,这样可以在之后找循环节来解决,然后确定 C C C,而接下来的问题就是我需要知道循环节。
于是正解用 e x K M P exKMP exKMP,于是……

大炮打蚊子—xinjun

于是参照了字符串的题目,想到哈希。
我可以预处理出到每个位置的哈希值,然后用前缀和的方式将它求出每一段的哈希值,可以 O ( 1 ) O(1) O(1)比较两段字符串是否相等。可以快速地找循环节。
比如我当前枚举的 A B AB AB,那么在 A B AB AB后面的等长的哈希值判一判就可以了。

解决上述问题后,可以来看奇偶的限制了。

首先很简单可以得出: A A A是整个串的前缀, C C C是整个串的后缀,可以预处理出每个位置前后缀的奇偶,然后进行判断。
看似好像解决了吼,但是!会遇到这种情况:有多个循环节,但是我可以把最后一个循环节算成 C C C,这似乎也是可以的。如果不考虑奇偶,就是循环节的个数,但是考虑了奇偶之后就显得麻烦了。(这里添一嘴,我一开始想过为什么不要考虑把循环节合并后又是几个循环节的情况,但是我枚举的是循环节的长度,两个或者几个合并就不在当前的枚举范围内,之后会算进去的)

那么我们不妨将之情形展示一下,比如,我当前判断 ( A B ) i C (AB)^iC (AB)iC合法,奇偶也合法,那么 ( A B ) i − 1 A B C (AB)^{i-1}ABC (AB)i1ABC是否合法呢?不知道,因为 B B B的奇偶性我不知道,之前的判定是依靠前缀的奇偶来解决 A A A的奇偶,如果再判 B B B的显然会超时1,那怎么办?
别急,继续拆一个, ( A B ) i − 2 A B A B C (AB)^{i-2}ABABC (AB)i2ABABC是否合法?合法,并且一定合法2。为什么?因为 A B A B ABAB ABAB一定每个字母都出现偶数次,对后缀的奇偶不产生影响。

所以定义函数 o d l ( S ) odl(S) odl(S)表示 S S S的奇偶性,则 o d l ( S ) = o d l ( A A S ) odl(S)=odl(AAS) odl(S)=odl(AAS)
所以只要判断后缀就可以推广至全部了。

最后一个问题,枚举了 A B AB AB后,无法确定 A , B A,B A,B的分割点。确切地说,枚举了一个串 S S S,但是这个 S S S可以有很多种方式拆成 A A A B B B的形式,因此我们需要枚举 A A A,然后判断是否合法。
结果超时了。那么结果怎么办?诶发现字母是只有26个的,我可以存一下到目前为止,每个出现奇数次字母个数相等的前缀个数,即用 t [ i ] t[i] t[i]表示前几个前缀中有多少个出现奇数次字母个数是 i i i个的。然后对于每个后缀,都枚举一遍比它小的个数,直接加在 a n s ans ans中即可。

代码

P S : 本 代 码 可 能 存 在 姿 势 不 好 等 问 题 , O J 不 稳 定 的 时 候 可 能 会 T , \color{Red}{PS:本代码可能存在姿势不好等问题,OJ不稳定的时候可能会T,} PS:姿OJT
   希 望 各 位 自 己 写 , 仅 仅 提 供 参 考 。 \,\,\quad\quad\color{Red}{希望各位自己写,仅仅提供参考。}
或者趁OJ不注意,卡过去,我就是这么在luogu上过的233,以后发誓再(yi)也(ding)不(yao)卡评测。

#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
using namespace std;
const int MAXN=(1<<20)+100;
const int B=499;
char s[MAXN];

ull h[MAXN],base[MAXN];//ull自动溢出
ull geth(int l,int r){return h[r]-h[l-1];}

int odp[MAXN],odl[MAXN],cnt[30],t[30];
//odp是前缀奇数个数odd_pre
//同理odd_last
int main()
{
//	freopen("string.in","r",stdin);
//	freopen("string.out","w",stdout);
	int T,len;
	scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);
		len=strlen(s+1);
		
		base[1]=1;h[0]=0;
		for(int i=1;i<=len;i++){
			h[i]=base[i]*s[i]+h[i-1];
			base[i+1]=base[i]*B;
		}
		
		memset(cnt,0,sizeof(cnt));
		odp[0]=0;
		for(int i=1;i<=len;i++){
			odp[i]=odp[i-1];
			if(cnt[s[i]-'a']&1) odp[i]--;
			else odp[i]++;
			cnt[s[i]-'a']++;
		}
		memset(cnt,0,sizeof(cnt));
		odl[len+1]=0;
		for(int i=len;i>=1;i--){
			odl[i]=odl[i+1];
			if(cnt[s[i]-'a']&1) odl[i]--;
			else odl[i]++;
			cnt[s[i]-'a']++;
		}
		
		memset(t,0,sizeof(t));
		ll ans=0;t[1]=1;
		for(int x=2;x<len;x++){
			int tm=0,st=0;
			ull cur=geth(1,x);
			while(geth(st+1,st+x)==cur*base[st+1]&&st+x<len) tm++,st+=x;//不断向后找
			st-=x;
			for(int i=odl[st+x+1];i>=0;i--) ans+=((tm+1)/2)*t[i];
			for(int i=odl[st+1];i>=0;i--) ans+=(tm/2)*t[i];
			t[odp[x]]++;
		}printf("%lld\n",ans);
	}
}

END

大家可以去你谷上看看卡常技巧,毕竟不是正解。
不过这题这样的思维是真心不错!!


  1. 你可能会说可以用从最后一个 A A A的后缀情况解决,但是可以试试,这也是需要枚举的,在没有下面的结论之前,是无法很好地解决的。 ↩︎

  2. 这是建立在 ( A B ) i C (AB)^iC (AB)iC已经合法的基础上的。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值