随笔 - 34,  文章 - 63,  评论 - 0,  阅读 - 4070
题号:NC15253
时间限制: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行表示每个串的答案
示例1

输入

复制
abab
2
abababab
ababcbaba

输出

复制
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;//当时就这样子写的。

posted on   浅唱\,,笑竹神易  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示