1455:【例题1】Oulipo
典型例题:给出两个字符串s1,s2,求s1在s2中出现多少次
ps.求子串的hash公式
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//字符串hash模板题
char s1[maxn],s2[maxn];
ull h[maxn];
ull power[maxn]; //每一步要乘的
ull b=27,mod=1<<31;
//只有大写字母
int main(){
power[0]=1;
for(int i=1;i<=1000000;i++) power[i]=power[i-1]*b;
int t;
scanf("%d",&t);
while(t--){
scanf("%s",s1+1);
scanf("%s",s2+1);
int n=strlen(s1+1);
int m=strlen(s2+1);
h[0]=0;
ull ans=0,s=0;
for(int i=1;i<=m;i++){ //算出s2的hash值 每一位
h[i]=(h[i-1]*b+(ull)(s2[i]-'A'+1))%mod;
}
for(int i=1;i<=n;i++) s=(s*b+(ull)(s1[i]-'A'+1))%mod; //s1的hash值
for(int i=0;i<=m-n;i++){
if(s==h[i+n]-h[i]*power[n]) ans++;
}
printf("%d\n",ans);
}
return 0;
}
1456:【例题2】图书管理

这道题可以直接用map来做,map<ull,int> a;
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=30010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
int n;
int mod1=100005;
int k1=233317;
map<ull,int> a; //用map来做
char op[10],ss[210];
int main(){
scanf("%d",&n);
//int num=0;
while(n--){
scanf("%s ",op);
gets(ss);
ull num=0;
for(int i=0;i<strlen(ss);i++) //求出hash值
num=(num*k1+(int)ss[i]);
if(op[0]=='f'){
if(a[num]==1) printf("yes\n");
else printf("no\n");
}
else a[num]=1;
}
return 0;
}
1457:Power Strings
给定若干个长度 ≤106 的字符串,询问每个字符串最多是由多少个相同的子字符串重复连接而成的。如:ababab 则最多有 3 个 ab 连接而成。
abcd 1 aaaa 4 ababab 3 .
询问每个字符串最多是由多少个相同的子字符串重复连接而成的 求最短重复子序列(这样才能保证最多嘛
这道题试问我们一个串有几个循环节。循环节就是指相等的(真)前缀和(真)后缀的个数。我们知道,
kmp过程中的next[i]是这个意义:0-i-1位中相等的真前后缀个数。那么next[len]就是指0-len-1位中相等的真前后缀个数。
所以第一种做法就是,求next数组
所以要的最短重复子序列就是len-next[len]
if(len%(len-next[len])==0) printf("%d\n",len/(len-next[len]));
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3fffffff;
typedef long long LL;
//询问每个字符串最多是由多少个相同的子字符串重复连接而成的 求最短重复子序列
//这道题试问我们一个串有几个循环节。循环节就是指相等的(真)前缀和(真)后缀的个数。我们知道,
//kmp过程中的next[i]是这个意义:0-i-1位中相等的真前后缀个数。那么next[len]就是指0-len-1位中相等的真前后缀个数。
char s[maxn];
int next[maxn],len;
void getnext(){
int j=-1;
int i=0;
next[0]=-1;
while(i<len){
if(j==-1||s[i]==s[j]){
i++;j++;
next[i]=j;
}
else j=next[j];
}
}
int main(){
while(1){
scanf("%s",s);
if(s[0]=='.') break;
memset(next,0,sizeof(next));
len=strlen(s);
getnext();
if(len%(len-next[len])==0) printf("%d\n",len/(len-next[len]));
else printf("1\n");
}
return 0;
}
第二种做法:普通做法,hash,判断连续k个字母的hash值是不是都是一样的,求子串的hash值
bool check(ull u,int k),分别是对应的hash值,还有就是这个重复子串的长度
//普通做法
#include<stdio.h>
#include<string.h>
using namespace std;
const int maxn = 1e6+10;
typedef unsigned long long ull;
char s1[maxn];
ull power[maxn],h[maxn];
ull n,s,base=131,mod=1<<31;
int check(ull v,int k){
for( ull i=0; i<n; i+=k ){
if(h[i+k]-h[i]*power[k]!=v) return 0; //计算字串的hash值
}
return 1;
}
int main(){
power[0]=1;
for( int i=1; i<=101000; i++ ) power[i]=power[i-1]*base;
while(scanf("%s",s1+1)){
if(s1[1]=='.') break;
n=strlen(s1+1);
h[0]=0;
for( int i=1; i<=n; i++ ) h[i]=h[i-1]*base+(ull)(s1[i]-'A'+1);
for( int i=1; i<=n; i++ ){
if(n%i==0){
if(check(h[i],i)==1){
printf("%d\n",n/i);break;
}
}
}
}
}
1458:Seek the Name, Seek the Fame
给定若干字符串(这些字符串总长 ≤4×105 ),在每个字符串中求出所有既是前缀又是后缀的子串长度。
例如:ababcababababcabab,既是前缀又是后缀的:ab,abab,ababcabab,ababcababababcabab。
前后缀:肯定就想到了next数组
这道题不需要重复求出next数组,只需要不断向前移动next
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=4e5+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//又是next数组?
char s[maxn];
int next[maxn];
int len=0;
void getnext(){
int i=0,j=-1;
next[0]=-1;
while(i<len){
if(j==-1||s[i]==s[j]) {
i++;j++;
next[i]=j;
}
else j=next[j];
}
}
int op[maxn];
int main(){
while(~scanf("%s",s)){
memset(next,0,sizeof(next));
len=strlen(s);
getnext();
int num=0;
memset(op,0,sizeof(op));
//不用循环做next数组,直接取呀!!!
for(int i=len;next[i]!=-1;){
op[++num]=i; //len也写进来了
i=next[i];
}
for(int i=num;i>=1;i--) printf("%d ",op[i]);
printf("\n");
}
return 0;
}
1459:friends

根据题目意思,我们可以先求出字符串的HASH值,再枚举插入字符的位置然后判断字符串去掉这个字符后,用HASH计算两串是否相等的办法,判断能否分成两个相同部分。
分为三种情况:插在左边、插在右边、插在中间
然后计算三种情况下的hash值,看去掉当前这个字符,两边的hash值是不是相等
重点是怎样求中间空一个位置的串的hash值
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ll;
char s[2100000];
ll len,ans,sum,x,b=1e9+7,p[2100000],a[2100000];
int main()
{
scanf("%lld%s",&len,s+1);
if(len%2==0)
{
printf("NOT POSSIBLE\n");
return 0;
}
p[0]=1;
a[0]=ans=0;
for(int i=1;i<=len+10;i++)
p[i]=p[i-1]*b;
for(int i=1;i<=len;i++)
a[i]=a[i-1]*b+s[i];
for(int i=1;i<=len;i++)
{
bool bk=false;
if(i<(len+1)/2)
{
if(a[i-1]*p[(len+1)/2-i]+a[(len+1)/2]-a[i]*p[(len+1)/2-i]
==a[len]-a[(len+1)/2]*p[len/2])
sum=a[len]-a[(len+1)/2]*p[len/2],x=i,bk=true;
}
else
if(i>(len+1)/2)
{
if(a[len/2]
==(a[i-1]-a[len/2]*p[i-1-len/2])*p[len-i]+a[len]-a[i]*p[len-i])
sum=a[len/2],x=i,bk=true;
}
else
if(i==(len+1)/2)
{
if(a[len/2]==a[len]-a[(len+1)/2]*p[len-i])
sum=a[len/2],x=i,bk=true;
}
if(ans&&bk)
{
if(ans!=sum)
{
printf("NOT UNIQUE\n");
return 0;
}
}
else
ans=sum;
}
if(!ans)
printf("NOT POSSIBLE\n");
else
{
for(int i=1;i<=len/2;i++)
{
if(i<x)
printf("%c",s[i]);
else
printf("%c",s[i+1]);
}
printf("\n");
}
return 0;
}
1460:A Horrible Poem
这道题也是求最短循环节的,但是有两个点会超时(怎么过的

P3538 [POI2012]OKR-A Horrible Poem - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
1、循环节一定是长度的约数
2、如果n是一个循环节,那么k*n也必定是一个循环节(关键所在)
3、n是[l,r]这一段的循环节 的充要条件是 [l,r-n]和[l+n,r]相同(利用这个性质我们在判断是否为循环节是可以做到O(1))
所以我们可以在求出这个区间的长度之后,判断它的每个约数是否是循环节(应用性质3),并且因为性质1,它的约数是循环节,原串一定也是。
所以只要不断除以质因数(相当于从大到小枚举约数),缩小L的长度,最后就是最小的长度。
而一个重要的点在于,用上面方法来枚举约数是为了避免tle
在求它的质因数的时候,可以通过线性筛的过程求得,将时间法度由根号n 降为logn。
注意这里枚举到全长。
这样通过nxt数组不断回跳,就可以找出所有质因数(多次乘方的也可以)
判定循环节的时候可以使用hash。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define seed 13131
#define maxn 500005
using namespace std;
typedef unsigned long long ull;
char s[500005];
ull s1[500005];
ull ss[500005];
int sushu[500005];
int used[500005];
int nxt[500005];
int k;
int ys[500005];
void pri()
{
for(int i = 2;i<= maxn;i++)
{
if(used[i] == 0)
{
sushu[++k] = i;
nxt[i] = i;
}
for(int j = 1;j <= k && (long long)i*sushu[j]<= maxn;j++)
{
used[i*sushu[j]] = 1;
nxt[i*sushu[j]] = sushu[j];
if(i%sushu[j] == 0)
{
break;
}
}
}
}
int check(int l1,int r1,int l2,int r2)
{
if(s1[r1]-ss[r1-l1+1]*s1[l1-1] == s1[r2]-ss[r2-l2+1]*s1[l2-1])
{
return 1;
}
return 0;
}
int main()
{
int n;
scanf("%d", &n);
scanf("%s",s+1);
int q;
scanf("%d", &q);
for(int i = 1;i <= n;i++)
{
s1[i] = s1[i-1]*seed+s[i]-'a'+1;
}
ss[0] = 1;
for(int i = 1;i <= n;i++)
{
ss[i] = ss[i-1]*seed;
}
pri();
for(int i = 1;i <= q;i++)
{
int l,r;
scanf("%d%d", &l, &r);
int len = r-l+1;
int tt = 0;
while(len != 1)
{
ys[++tt] = nxt[len];
len = len/nxt[len];
}
len = r-l+1;
for(int j = 1;j <= tt;j++)
{
int t = len/ys[j];
if(check(l,r-t,l+t,r) == 1)
{
len = t;
}
}
printf("%d\n",len);
}
return 0;
}
1461:Beads

看怎样除这个,能获得多种类型的子串
有一点要注意,就是正反串的hash值,1 2 3 和3 2 1是一样的,所以要正反都求一次hash值,用map来判断,但是有一点,应该是用h1*h2的值作为map的键值
不然一个map存正向的,一个map存反向的,会超时
可以想到存储格式:map
正向的子串hash:suml[j]-suml[j-i]*p[i];
反向的子串hash:sumr[j-i+1]-sumr[j+1]*p[i]
#include <bits/stdc++.h>
typedef unsigned long long ll;
using namespace std;
int n,a[200005],ans[200005],total=1;
ll p[200005],suml[200005],sumr[200005],b=1000000007;
map<ll,int>mp;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int i,j,c=0,maxn=1;
cin>>n;
p[0]=1;
for(i=1;i<=n;i++)
{
cin>>a[i];
p[i]=p[i-1]*b,suml[i]=suml[i-1]*b+a[i];
}
for(i=n;i>=1;i--)
{
sumr[i]=sumr[i+1]*b+a[i];
}
for(i=1;i<=n;i++) /**< i用来表示字串长度,最大为n,不可以用n/2*/
{
mp.clear();
c=0;
for(j=i;j<=n;j+=i)
{
ll h1=suml[j]-suml[j-i]*p[i];
ll h2=sumr[j-i+1]-sumr[j+1]*p[i];
ll h3=h1*h2; /**< 正反认为同一个串,所以如果两串相同,
正向hash值h1和反向hash值h2的乘积的值必然一样 */
if(!mp[h3])
{
c++;
mp[h3]=1;
}
}
if(c>maxn)
{
maxn=c;
total=1;
ans[total++]=i;
}
else if(c==maxn)
{
ans[total++]=i;
}
}
cout<<maxn<<' '<<total-1<<endl;
for(i=1;i<total;i++)
cout<<ans[i]<<' ';
return 0;
}
1462:Antisymmetry

0\1取反和对称操作的前后顺序显然不影响,先考虑对称操作再考虑取反,于是可以发现子串长度显然是偶数而且关于对称轴0/1对称(一边为0则另一边为1)。
算法1: 枚举对称轴,二分+hash。时间复杂度O(nlog(n))。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define seed 13131
#define maxn 500010
#define LL long long
using namespace std;
typedef unsigned long long ull;
char s[maxn];
int a[maxn],b[maxn],n;
LL ha[maxn],hb[maxn],p[maxn]; //原串和原串的hash 取反后的串和hash
const int md=1e9+7;
LL geta(int l,int r){ //原串的子串值
return (ha[r]-ha[l-1]*p[r-l+1]%md+md)%md;
}
LL getb(int l,int r){
return (hb[l]-hb[r+1]*p[r-l+1]%md+md)%md;
}
int query(int x){
int l=1,r=n/2;
while(l<=r){
int mid=(l+r)>>1;
if(mid>=1&&x+mid<=n&&geta(x-mid+1,x+mid)==getb(x-mid+1,x+mid)) l=mid+1;
else r=mid-1;
}
return r;
}
int main(){
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
for(int i=1;i<=n;i++) b[i]=1-a[i];
ha[0]=1;hb[n+1]=1;p[0]=1;
for(int i=1;i<=n;i++) ha[i]=(ha[i-1]*seed%md+a[i])%md,p[i]=p[i-1]*seed%md;
for(int i=n;i>=1;i--) hb[i]=(hb[i+1]*seed%md+b[i])%md;
LL ans=0;
for(int i=1;i<=n;i++){
ans+=query(i);
}
printf("%lld\n",ans);
return 0;
}
算法2: manacher算法,O(n)的时间算出所有子串最长回文子串长度(此题改下判断条件就好)。
!!!!怎么想到马拉车算法的?因为要旋转,先不考虑取反,如果连在一起是对称的话,就说明是反的,所以想到了马拉车算法https://blog.csdn.net/yanghongMO/article/details/52673305
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn= 500010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//先全部反转,两个串都求hash,然后比较子串,哪些两个hash值相等
int n;
/*
0\1取反和对称操作的前后顺序显然不影响,先考虑对称操作再考虑取反,于是可以发现子串长度显然是偶数而且关于对称轴0/1对称(一边为0则另一边为1)。
算法1: 枚举对称轴,二分+hash。时间复杂度O(nlog(n))。
算法2: manacher算法,O(n)的时间算出所有子串最长回文子串长度(此题改下判断条件就好)。
*/
//理解马拉车算法呀
//https://blog.csdn.net/yanghongMO/article/details/52673305
char c[maxn*2];
char s[maxn];
int len[maxn*2];
int main(){
scanf("%d %s",&n,s+1);
c[0]='$';
for(int i=1;i<=n;i++){
c[i*2-1]='#';
c[i*2]=s[i];
}
c[2*n+1]='#';
c[2*n+2]='$';
int mx=0,po=0;
ull ans=0;
for(int i=1;i<=2*n;i+=2){
if(mx>i) len[i]=min(mx-i,len[2*po-i]);
else len[i]=1;
while((c[i+len[i]]==c[i-len[i]]&&c[i+len[i]]=='#')||(c[i-len[i]]-'0'+c[i+len[i]]-'0'==1)) len[i]++; //反对称的要求就是从中间向两边扩展的每两个数相加都是1
//由于我们需要的是长度为偶数的回文串,所以只需考虑 ‘#’ 的位置(就是Manacher添加的那些字符).
if(len[i]+i>mx){
mx=len[i]+i;
po=i;
}
ans+=len[i]/2;
}
printf("%lld\n",ans);
return 0;
}
1463:门票


有三种方法,第一种会超时
(1)第一种就是用一个散列表,如果存在,就输出,不存在就保存
注意这个存储方法很像链式前向星
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int P=100003; int a,b,c,x=1,s,last[P];
struct node { int x,pre; }; vector<node>f;
//这里用vector是想优化一下空间,事实上数组即可
//有一个点通不过
//https://blog.csdn.net/bcr_233/article/details/100167093
int find() {
for (int i=last[s]; ~i; i=f[i].pre)
if (f[i].x==x) return 1;
return 0;
}
int main() {
scanf("%d%d%d",&a,&b,&c);
memset(last,-1,sizeof last);
f.push_back({1,-1}); last[1]=0;
for (int i=1; i<=2000000; ++i) {
x=(1ll*a*x+x%b)%c; s=x%P;
if (find()) return printf("%d\n",i),0;
f.push_back({x,last[s]}); last[s]=f.size()-1;
}
puts("-1"); return 0;
}
(2)双hash,过了
双哈希一般是不会被卡掉的,直接认为是正确的就好了
不过这种做法空间是线性的,仍然需要占用 2MB+ 的空间,事实上我们有更优的解法。
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e6;
const int INF=0x3fffffff;
typedef long long LL;
//双hash
//双哈希一般是不会被卡掉的,直接认为是正确的就好了
//不过这种做法空间是线性的,仍然需要占用 2MB+ 的空间,事实上我们有更优的解法。
const int P1 =249439;
const int P2 = 414977;
int a,b,c;
int vis1[P1],vis2[P2];
int p1,p2;
int main(){
scanf("%d %d %d",&a,&b,&c);
p1=1;
p2=1;
vis1[1]=1;vis2[1]=1;
for(int i=1;i<=maxn;i++){
p1=(1ll*p1*a+p1%b)%c;
p2=(1ll*p2*a+p2%b)%c;
int m1=p1%P1;
int m2=p2%P2;
if(vis1[m1]&&vis1[m1]==vis2[m2]){
printf("%d\n",i);
return 0;
}
if(!vis1[m1]) vis1[m1]=i;
if(!vis2[m2]) vis2[m2]=i; //是同一个数产生的
}
printf("-1\n");
return 0;
}
(3)数学方法
观察数列的递推式可以看出,这个数列是存在循环节的。
//首先找到循环节长度,然后开始找循环开始的地方
//也就是循环节的起点,
根据题目要求,循环节长度不能超过 2e6,所以 a2e6一定在这个循环里面。
于是我们找到第一个和 a2e6等的元素,它们之间的距离就是循环节的长度 len
如果没有找到则说明循环节长度大于2e6,此时输出-1
于是我们可以分别从 a0和 alen开始同时往后计算,找到的第一对相等元素即是答案。
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=2e6;
const int INF=0x3fffffff;
typedef long long LL;
//数学做法
//观察数列的递推式可以看出,这个数列是存在循环节的。
//首先找到循环节长度,然后开始找循环开始的地方
//也就是循环节的起点,
/*
根据题目要求,循环节长度不能超过 2e6,所以 a22e6一定在这个循环里面。
于是我们找到第一个和 a2e6等的元素,它们之间的距离就是循环节的长度 len
如果没有找到则说明循环节长度大于2e6,此时输出-1
于是我们可以分别从 a0和 alen开始同时往后计算,找到的第一对相等元素即是答案。
*/
int a,b,c;
int s1=1,s2=1;
int len=0; //循环节长度
int main(){
scanf("%d %d %d",&a,&b,&c);
//首先计算a[inf]
for(int i=1;i<=maxn;i++){
s1=(1ll*s1*a+s1%b)%c;
}
if(s1==1){ //首先判断a[0]是否与a[inf]相等
len=INF;
}
for(int i=1;i<maxn;i++){ //注意这里不能取等号
s2=(1ll*s2*a+s2%b)%c;
if(s1==s2){
len=maxn-i; //记录两元素的距离
}
} //这样更新以后len就是最短循环节长度
//循环结束后,len更新至最小值,即是循环节长度
if(!len){
printf("-1\n");return 0; //没有更新len,说明循环节长度大于inf
}
s1=s2=1;
for(int i=1;i<=len;i++) s2=(1ll*s2*a+s2%b)%c; //计算a[len]的时候的值
for(int i=0;i<=maxn;i++){
if(s1==s2){
printf("%d",len+i);
return 0;
}
s1=(1ll*s1*a+s1%b)%c; //分别从0、len开始算,直到算到相等的地方,这里就是循环开始的地方
s2=(1ll*s2*a+s2%b)%c;
}
printf("-1");
return 0;
}
1464:收集雪花

用map就可以了,有点像尺取法
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//可以直接用map
//我这个连题目都能读错的混账
int n;
int a[maxn],l=1,r;
map<int,int> mp; //判断存不存在
int main(){
scanf("%d",&n);
int len=0;
for(r=1;r<=n;r++){
scanf("%d",&a[r]);
if(mp.find(a[r])!=mp.end()){ //存在
while(a[l]!=a[r]) mp.erase(mp.find(a[l++])); //一直删掉直到相同,然后也把相同的删掉
l++;
}
else mp[a[r]]=1;
len=max(len,r-l+1); //记录最长的种类数
}
printf("%d",len);
return 0;
}
posted on
浙公网安备 33010602011771号