9.29 a.m.小结

T1:问题 A: 字符串匹配

题目描述

现定义两个仅由大写字母组成的字符串的匹配程度如下:将某一字符串的首字符与另一字符串的某一字符对齐,然后后面的字符也一一对齐,直至某一字符串的串尾为止。对于每一组对齐的两个字符,若这两个字符相等,则计数。匹配程度为每种对齐方法的计数的最大值。最后计算这个匹配程度的 2 倍,与两串总长度的最大比值。

输入

多组数据,每组一行两个字符串,中间用一个空格隔开,以 -1 结束输入。

输出

对于每组数据,输出两个字符串的最大匹配数与两串总长度的比值,具体格式见输出样例。

样例输入

CAR CART
TURKEY CHICKEN
MONEY POVERTY
ROUGH PESKY
-1

样例输出

appx(CAR,CART) = 6/7
appx(TURKEY,CHICKEN) = 4/13
appx(MONEY,POVERTY) = 1/3
appx(ROUGH,PESKY) = 0

题解

这道题主要看两个字符串的对齐情况,一共只有n种情况,而内部枚举点也有n个,因此对于每一组字符串来说,效率是O(O^2)的。可以用一种比较简单的方式处理这个过程。定义一个数组nxt,然后以i-j的差与1005的和作为下标,表示i与j的对齐情况,加1005是为了不出现负下标,这样直接 O(O^2)枚举字符串的点,然后累加答案到对应的nxt数组中,最后直接扫描所有nxt求最大值就可以了,注意每次输入后都要重置nxt数组。

参考代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
char s1[1000100],s2[1000100];
int nxt[1000100],len1,len2;
int gcd(int a,int b)
{ return b==0?a:gcd(b,a%b); }
int max1(int p,int q) { return p>q?p:q; }
int comp1()
{
	int maxn=0;
	for(int i=1;i<=len1;i++)
	{
		for(int j=1;j<=len2;j++)
		{
			if(s1[i-1]==s2[j-1]) ++nxt[j-i+1050];
		}
	}
	for(int i=1;i<=len1+len2+1050;++i)
		maxn = max1(maxn,nxt[i]);
	return maxn;
}
int main()
{
	while(1)
	{
		memset(nxt,0,sizeof(nxt));
		scanf("%s",s1);
		if(s1[0]=='-') break;
		scanf("%s",s2);
		int sizes=0;
		len1=strlen(s1);
		len2=strlen(s2);
		sizes=len1+len2;
		int st=2*comp1();
		int gcds=gcd(sizes,st);
		if(st==0)
		printf("appx(%s,%s) = 0\n",s1,s2);
		else if(sizes==gcds)
		printf("appx(%s,%s) = %d\n",s1,s2,st/gcds);
		else
		printf("appx(%s,%s) = %d/%d\n",s1,s2,st/gcds,sizes/gcds);
	}
	return 0;
}

T2:问题 C: 兔子与兔子

题目描述

很久很久以前,森林里住着一群兔子。
有一天,兔子们想要研究自己的 DNA 序列。
我们首先选取一个好长好长的 DNA 序列(小兔子是外星生物,DNA 序列可能包含 26 个小写英文字母)。
然后我们每次选择两个区间,询问如果用两个区间里的 DNA 序列分别生产出来两只兔子,这两个兔子是否一模一样。
注意两个兔子一模一样只可能是他们的 DNA 序列一模一样。

输入

第一行输入一个 DNA 字符串 S。
第二行一个数字 m,表示 m 次询问。
接下来 m 行,每行四个数字 l1,r1,l2,r2,分别表示此次询问的两个区间,注意字符串的位置从1开始编号。

输出

对于每次询问,输出一行表示结果。
如果两只兔子完全相同输出 Yes,否则输出 No(注意大小写)。

样例输入

aabbaabb
3
1 3 5 7
1 3 6 8
1 2 1 2

样例输出

Yes
No
Yes

提示

【数据范围】

1≤length(S),m≤1000000

题解

显然,这是一道字符串哈希的题。由于我对字符串哈希一点感觉都没,所以考试考这题直接炸掉,花两个小时重新推导,并且不停调参才对。unsigned long long可以对大数和负数自动取模,所以只用找进制数p。其实有个大佬说得好,把字符串看成p进制数再联系二进制的左移、右移来理解哈希值:hash(x)=r-(l-1)\times p^^{r-l+1}。由于对于加法运算和乘法运算,取模不会影响相对大小,所以这就求出了从l到r区间内的哈希值。可以同时选几个哈希数,以保证正确性。参考代码中只选择了一个比较大的质数。

参考代码

#include<cstdio>
#include<cstring>
#define p 577ll
#define mod 4837647ll
using namespace std;
char s[1001000];
int t;unsigned long long a[1001000],lg[1001000];
int main()
{
	scanf("%s",s);
	int lens=strlen(s);
	lg[0]=1ll;
	for(int i=1;i<=lens;i++)
	{
		a[i]=a[i-1]*p+1ll*(s[i-1]-'a'+1);
	}
	for(int i=1;i<=lens;i++)
	{
		lg[i]=lg[i-1]*p;
	}
	scanf("%d",&t);
	while(t--)
	{
		int l1,r1,l2,r2;
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		if((a[r1]-a[l1-1]*lg[r1-l1+1])==(a[r2]-a[l2-1]*lg[r2-l2+1]))
		printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

T3:问题 F: 周期

题目描述

For each prefix of a given string S with N characters (each character has an ASCII code between 97 and 126, inclusive), we want to know whether the prefix is a periodic string. That is, for each i (2 <= i <= N) we want to know the largest K > 1 (if there is one) such that the prefix of S with length i can be written as AK ,that is A concatenated K times, for some string A. Of course, we also want to know the period K.

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如”abaab”共有5个前缀,分别是a, ab, aba, abaa, abaab。

我们希望知道一个N位字符串S的前缀是否具有循环节。

换言之,对于每一个从头开始的长度为 i (i>1)的前缀,是否由重复出现的子串A组成,即 AAA…A (A重复出现K次,K>1)。

如果存在,请找出最短的循环节对应的K值(也就是这个前缀串的所有可能重复节中,最大的K值)。

输入

The input consists of several test cases. Each test case consists of two lines. The first one contains N (2 <= N <= 1 000 000) – the size of the string S.The second line contains the string S. The input file ends with a line, having the number zero on it.

输入包括多组测试数据,每组测试数据包括两行。

第一行输入字符串S的长度N。

第二行输入字符串S。

输入数据以只包括一个0的行作为结尾。

输出

For each test case, output "Test case #" and the consecutive test case number on a single line; then, for each prefix with length i that has a period K > 1, output the prefix size i and the period K separated by a single space; the prefix sizes must be in increasing order. Print a blank line after each test case.

对于每组测试数据,第一行输出 “Test case #” 和测试数据的编号。

接下来的每一行,输出具有循环节的前缀的长度i和其对应K,中间用一个空格隔开。

前缀长度需要升序排列。

在每组测试数据的最后输出一个空行。

样例输入

3
aaa
12
aabaabaabaab
0

样例输出

Test case #1
2 2
3 3

Test case #2
2 2
6 2
9 3
12 4

提示

2≤N≤1000000

题解

这道题就是考KMP经典算法。首先求出所有的nxt值,然后有一个定理。

定理:如果字符串长度为n,那么n-nxt【n】的值就是最短循环节的长度。注意,如果最短循环节是abc,那么abcabcabcabc的字串cabcabcacb求出的值也是abc(字串也可以)。

证明过程略(推荐画图的方法,之前有一次详细写过,这里就不多说了(有点说不清楚))

如果说一个前缀i有循环节,那么能够求出i-nxt【i】作为最小循环节的长度,如果恰好i能将其整除,那么就可以说明这个前缀具有最小循环节,长度为i-nxt[i]。因此只需要在跑KMP的过程中顺便求一求答案就可以了。

参考代码

#include<cstdio>
#include<cstring>
#include<string>
#define ull unsigned long long
#define p 4354767ll
#define INF 999999999999999ll
using namespace std;
template <typename T> inline void wr(T x)
{
	if(x<0) putchar ('-') ,x=-x;
	if(x>9) wr(x/10);
	putchar (x%10+'0');
}
int len,cases=0,nxt[1001000],j0;
char s[1001000];
ull a[1001000],lg[1001000];
long long maxn;
int main()
{
	lg[0]=1ll;
	register int i;
	for(i=1;i<=1000000;i++)
	lg[i]=lg[i-1]*p;
	while(1)
	{
		memset(nxt,0,sizeof(nxt));
		cases++;
		scanf("%d",&len);
		if(len==0) break;
		printf("Test case #%d\n",cases);
		scanf("%s",s+1);
		nxt[1]=0;int j=0;
		for(i=2,j=0;i<=len;i++)
		{
			while(j>0&&s[i]!=s[j+1]) j=nxt[j];
			if(s[i]==s[j+1]) j++;
			nxt[i]=j;
		}
        //变量名字取得差不要打我
		for(int _1=2;_1<=len;_1++)
		{
			if(_1 % (_1 - nxt[_1]) == 0&&_1 / (_1 - nxt[_1])!=1)
			{
				printf("%d %d\n",_1,_1/(_1 - nxt[_1]));
			}
		}
		putchar('\n');		
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值