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 ",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 "); else printf("no "); } 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 ",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 ",len/(len-next[len])); else printf("1 "); } 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/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(" "); } 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 "); 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 "); return 0; } } else ans=sum; } if(!ans) printf("NOT POSSIBLE "); else { for(int i=1;i<=len/2;i++) { if(i<x) printf("%c",s[i]); else printf("%c",s[i+1]); } printf(" "); } return 0; }
1460:A Horrible Poem
这道题也是求最短循环节的,但是有两个点会超时(怎么过的
循环节的长度肯定就是总长度的因子,但是这个长度必须最小,而且有因为数据大,所以在求
因子的时候,需要用线性筛
https://blog.csdn.net/LIN452/article/details/52769834
#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=5e5+100; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //普通做法,最短循环节,判断连续k个字母的hash值是否相同 /* 循环节的长度肯定就是总长度的因子,但是这个长度必须最小,而且有因为数据大,所以在求 因子的时候,需要用线性筛 */ //读入优化 //过不了,会有两点超时 //https://blog.csdn.net/LIN452/article/details/52769834 inline void rd(int &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<1)+(res<<3)+(c^48); while(c=getchar(),c>=48); } inline void print(int x){ if(!x)return ; print(x/10); putchar((x%10)^48); } inline void sc(int x){ if(x<0){x=-x;putchar('-');} print(x); if(!x)putchar('0'); putchar(' '); } int len,q; int ll,rr; char s[maxn]; ull power[maxn],a[maxn],b=233; int vis[maxn],son[maxn][10]; //记录因数的 void inti(){ for(int i=2;i*i<maxn;i++){ if(!vis[i]){ for(int j=i;j<maxn;j+=i){ son[j][++son[j][0]]=i; //记录因子 vis[j]=1; //被筛掉 } } } } int gett(int l,int r){ //得到子串的hash值 return a[r]-a[l-1]*power[r-l+1]; } bool check(int st,int en,int len){ //以st为起点,len为循环节 ,递归比较,保证每len份都相同 if(st==en&&len==1) return 1; // return gett(st,en-len)==gett(st+len,en); } int main(){ scanf("%d %s",&len,s+1); power[0]=1; for(int i=1;i<=len;i++) { power[i]=power[i-1]*b; a[i]=a[i-1]*b+s[i]; } inti(); int ll,rr; scanf("%d",&q); while(q--){ rd(ll); rd(rr); int x=rr-ll+1; int y=rr-ll+1; int z=rr-ll+1; for(int j=1;j<=son[x][0];j++){ while(y%son[x][j]==0){ //能够整除这个长度 z/=son[x][j]; if(check(ll,ll+y-1,y/son[x][j])) y/=son[x][j]; //不断缩小这个循环节长度 else break; } while(z%son[x][j]==0) z/=son[x][j]; } if(z!=1){ if(check(ll,ll+y-1,y/z)) y/=z; } sc(y); } 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
01取反和对称操作的前后顺序显然不影响,先考虑对称操作再考虑取反,于是可以发现子串长度显然是偶数而且关于对称轴0/1对称(一边为0则另一边为1)。
算法1: 枚举对称轴,二分+hash。时间复杂度O(nlog(n))。
算法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; /* 01取反和对称操作的前后顺序显然不影响,先考虑对称操作再考虑取反,于是可以发现子串长度显然是偶数而且关于对称轴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 ",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 ",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 ",i); return 0; } if(!vis1[m1]) vis1[m1]=i; if(!vis2[m2]) vis2[m2]=i; //是同一个数产生的 } printf("-1 "); 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 ");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; }