时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
白兔有一个字符串T。白云有若干个字符串S1,S2..Sn。
白兔想知道,对于白云的每一个字符串,它有多少个子串是和T循环同构的。
提示:对于一个字符串a,每次把a的第一个字符移动到最后一个,如果操作若干次后能够得到字符串b,则a和b循环同构。
所有字符都是小写英文字母
输入描述:
第一行一个字符串T(|T|<=10^6)
第二行一个正整数n (n<=1000)
接下来n行为S1~Sn (|S1|+|S2|+…+|Sn|<=10^7),max(|S1|,|S2|,|S3|,|S4|,..|Sn|)<=10^6
输出描述:
输出n行表示每个串的答案
输出
复制5 2
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef unsigned long long ull; const int mas=1e6+5; const int p=131; ull ha[mas*2],T[mas*2],c[2*mas]; char v[mas*2]; int len,n,cnt; bool check(ull x) { int l=1,r=len; if(l==r&&ha[l]==x)return 1; else if(l==r&&ha[l]!=x)return 0; while(l<r) { int mid=l+r>>1; if(ha[mid]>=x)r=mid; else l=mid+1; } if(ha[r]==x)return 1; return 0; } int main() { cin>>v+1; len=strlen(v+1); c[0]=1; for(int i=1;i<=2*len;i++)//2倍的len,不是一倍的len c[i]=c[i-1]*p;//一倍也可以,因为只用到了c[len] for(int i=1;i<=len;i++) v[len+i]=v[i];//环化成链 for(int i=1;i<=2*len;i++) T[i]=T[i-1]*p+v[i]-'a'+1; //c[i]*=p; 问题 for(int i=1;i<=len;i++) ha[i]=T[i+len-1]-T[i-1]*c[len]; sort(ha+1,ha+len+1); //不是n是len n等于0; // for(int i=1;i<=len;i++) // cout<<ha[i]<<endl; //cout<<endl; scanf("%d",&n); while(n--) { cnt=0; scanf("%s",v+1); for(int i=1;i<=strlen(v+1);i++) T[i]=T[i-1]*p+v[i]-'a'+1; for(int i=1;i+len-1<=strlen(v+1);i++) { ull u=T[i+len-1]-T[i-1]*c[len]; if(check(u))cnt++; } cout<<cnt<<endl; } return 0; }
问题总结:
1.这是一道二分+hash的问题
2.记住整数二分的模板:
以后写二分的时候进只能按照模板来写。
1>整数二分:
模板1:
while(l<r) { int mid=l+r>>1; if(v[mid]>=x) r=mid; else l=mid-1; }
模板2:
while(l<r) { int mid=l+r+1>>1; if(v[mid]<=x) l=mid; else r=mid-1; }
补充:整数二分,最开始while()之前,需要先判断是否l==r?
补充:if()里边的判断语句两种情况都是>=或者<=(规范自己的代码)
补充:r=mid,mid=l+r; l=mid,mid=l+r+1;
补充:错误写法:
while(l<=r) { int mid=l+r>>1; if(v[mid]==x)return x; else if(v[mid]<x)r=mid-1; else l=mid+1; }
这样写不一定会返回答案,而且要求l<=r这个和快排和二分的模板不相同,容易出问题。
2>实数二分:
模板1:精度把控
while(l+eps<r) { double mid=l+r>>1; if(calc(mid))r=mid; else l=mid; }
精度把控的时候,eps =1e^(-(k+2))。这里的k是指题目要求的保留的几位小数。一般来说,如果想要满足题目要求,误差精度要在已经给的范围上100倍。
模板2:循环次数把控
for(int i=1;i<100;i++) { double mid=l+r>>1; if(calc(mid))r=mid; else l=mid; }
因为精度不太容易把控,所以采用次数二分,一般来说100次左右基本差不多。
3>三分求单峰函数极值:
double eps=1e-8; while(l+eps<r) { double mid=l+r>>1; double midl=mid-eps,midr=mid+eps; if(f(midl)<=f(midr))l=midl; else if(f(midl)>f(midr))r=midr; }
注意:
1>适用的范围:单峰函数求极值(峰值左侧严格的单调递增,峰值右侧严格的单调递减,不能存在相邻但是相等情况),单谷函数求极值(峰值左侧严格的单调递增,峰值右侧严格的单调递减,不能存在相邻但是相等情况)。
区间首先是l,r;
同时设置两个试探的指针,midl,midr;midl=mid-eps,midr=mid+eps;
f(midl)<f(midr),存在两种情况:1> l,r都在峰值的左侧。2> l在峰值的左侧,r在峰值的右侧。
不论哪种情况,峰值都在l的左侧,所以l=midl;
f(midl)>f(midr),存在两种情况:1> l,r都在峰值的右侧。2> l在峰值的左侧,r在峰值的右侧。
不论哪种情况,峰值都在r的左侧,所以r=midr;
f(midl)==f(midr)因为是严格单调递增的,所以midl和midr一定是位于峰值的两侧,所以放到左右两侧都可以。
2>为何要求单调递增?
如果不是单调递增的,f(midl)==f(midr),midl和midr可能都位于左侧,可能都位于右侧,也有可能是一个位于左侧,一个位于右侧。无法判断。
4>二分答案转换为判定:在整数和实数的基础上,再加上一个bool check()函数作为if(check())判断条件;
补充:bool函数返回类型,只有true和false。如果返回0,会默认改为flase,其他的数值都会默认的改为true;
3.二分不论这道题是否正确都会二分出结果,但是答案不一定有解。
4.做这道题的时候c[i]=c[i-1]*p,而且c[0]=1,不能是c[i]*=p;//当时就这样子写的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话