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()的。可以用一种比较简单的方式处理这个过程。定义一个数组nxt,然后以i-j的差与1005的和作为下标,表示i与j的对齐情况,加1005是为了不出现负下标,这样直接 O()枚举字符串的点,然后累加答案到对应的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进制数再联系二进制的左移、右移来理解哈希值:。由于对于加法运算和乘法运算,取模不会影响相对大小,所以这就求出了从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;
}