复读机 (repeat) [前缀函数] [字符串压缩]

题面:

        众所周知,人类的本质是复读机。

        有时候,小华会得到某个字符串S 。这时他会把S不断重复不断重复连成一个无限长的串kokodayo。比如说,小华现在得到一个串 ,他会一直复读,那么形成的字符串就是:

       同样众所周知的是,发送消息的字长是有限制的。如果要发送的串超过了长度限制,那么就只会发送这个串的一个前缀。比如说对于上述无限长的字符串,若长度限制是13 ,那么实际发出去的字符串是 kokodayokokod 。

        现在小华发出去了一个字符串T,但他不能确定他复读的字符串是什么了。他唯一知道的是,他复读的字符串,一定是m个字符串中的一个。他希望知道,在这m个字符串中,有多少个字符串可能是他复读后发送出去的呢?

输入:

第一行输入一个字符n,表示发送消息的长度限制。
第二行输入一个长度为n的字符串T,表示小华发送的消息。
第三行输入一个数m。表示字符串个数。
接下来m行,每行输入一个字符串S。

输出:

输出一行一个整数,表示m个串中经过复读并发出后能够形成T的串个数。

样例输入 :

9
abaabaaba
3
aba
ab
abaaba

样例输出:

2

约束条件:

对于 100% 的数据,1≤ n, m ≤ 10^6,1 ≤ |S| ≤ n,\sum |S| ≤ 10^7 。

题解:

        既然题目是要求复读串,显而易见是字符串压缩的问题。

        这里,首先抛出一个结论:对于一个长度为 n 的字符串 S ,它的最短的“压缩”表示 t,(即我们希望寻找一个最短的字符串 ,使得 S 可以被 t 的一份或多份拷贝的拼接表示),t 的长度为 n-p[n] 。其中, p[n] 是指 S 下标 n 处的前缀函数。

        因此,我们只需要求出 S 和 T 的最短“压缩”表示,然后比较看是否满足条件即可。

        关于“前缀函数”的定义和“字符串压缩”结论的证明,参考这里有更加严谨的解释,本文只作解题思路,不再赘述。

代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define endl '\n'
const int N=1e6+10;
char s[N],t[N];
int nxt[N],n,T,ans;
void getNxt(char p[N])
{
    // 获取字符串各下标对应的前缀函数
	int m=strlen(p+1);
	int j=0;
	nxt[1]=0;
	for(int i=2;i<=m;i++)
	{
		while(j>0&&p[j+1]!=p[i]) j=nxt[j];
		if(p[j+1]==p[i]) j++;
		nxt[i]=j;	
	}
} 
signed main() 
{
    cin>>n;
    scanf("%s",s+1);
    int mainlen=strlen(s+1);
    getNxt(s);
    // 根据结论,k 即是字符串 s 最短的“压缩”表示的长度
    int k=mainlen-nxt[mainlen];
    cin>>T;
    while(T--)
    {
    	scanf("%s",t+1);
   		int len=strlen(t+1);
   		if(len<mainlen)
   		{
   			if(len>k&&(len%k==0))
			{
                // 显然,t 的长度要能被 k 整除,才有可能是复读串
				getNxt(t);
                // 根据结论,tark 即是字符串 t 最短的“压缩”表示
				int tark=len-nxt[len];
                // 不仅 tark == k,而且两字符串的真前缀还得一一对应,t 才是复读串
				if(tark==k)
                {
                    int f=0;
                    for(int i=1;i<=tark;i++)
                    {
                        if(t[i]!=s[i])
                        {
                            f=1;
                            break;
                        }
                    }
                    if(!f) ans++;
                }
			}	
			else if(len==k)
			{
                // 如果字符串 t 和 s 最短的“压缩”表示一样长,那就暴力匹配
				int f=0;
				for(int i=1;i<=len;i++)
				{
					if(t[i]!=s[i])
					{
						f=1;
						break;
					}
				}
				if(!f) ans++;
			}
		}
		else if(len==mainlen)
		{
            // 如果字符串 s 和字符串 t 一样长,那自然也是暴力匹配
			int f=0;
			for(int i=1;i<=mainlen;i++)
			{
				if(t[i]!=s[i])
				{
					f=1;
					break;
				}
			}
			if(!f) ans++;
		}
	}
	cout<<ans;
    return 0;   
}
/**************************************************************
    Problem: 16214
    User: 2021UPC016
    Language: C++
    Result: 正确
    Time:943 ms
    Memory:7884 kb
****************************************************************/

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值