洛谷P3667 [USACO17OPEN] Bovine Genomics G 翻译+超超级详细讲解

原题链接 : 点我​​​​​​

这是一个经典的哈希(hash)问题没学过hash的可以先去学一下hash。推荐阅读链接:点我

 注:本文的代码全部使用的是自然溢出的hash法,读者如果不喜欢可以吧unsigned int 改为int将会溢出的地方对一个大质数(如100001651)取模

翻译:

题目

农场主约翰拥有 N 头有斑点的奶牛和 N 头没有斑点的奶牛。刚刚完成牛遗传学课程后,他确信他的奶牛身上的斑点是由牛基因组突变引起的。

农夫约翰花费巨资对他的奶牛的基因组进行了测序。每个基因组都是由四个字符 A、C、G 和 T 构建的长度为 M 的字符串。当他排列奶牛的基因组时,他得到了如下所示的表格,如下所示。

N=3 和 M=8:

位置: 1 2 3 4 5 6 7 8

位置         :1  2   3  4  5   6   7  8

斑点牛 1  : A  A  T  C  C  C  A  T
斑点牛 2   :A  C  T  T  G  C  A  A
斑点牛 3  : G  G  T  C  G  C  A  A

无斑点牛1: A  C  T  C  C  C  A  G
无斑点牛2: A  C  T  C  G  C  A  T
无斑点牛3 : A  C  T  T  C  C  A  T

 他仔细查看该表,认为从位置2到位置5的顺序足以解释斑点。也就是说,仅通过查看这些位置(即位置2 ……5)中的字符,农夫约翰就可以预测出他的哪些奶牛有斑点,哪些没有斑点。例如,如果他在这些位置看到字符GTCG,他就知道那头奶牛一定是斑点的。请帮助FJ找到可以解释斑点的最短位置序列的长度。

输入格式

输入的第一行包含 (1≤N≤500) 和 M(3≤M≤500)。接下来N 行各包含一串 M 个字符;这些描述了斑点奶牛的基因。最后N行表示了奶牛的基因。没有一头斑点牛与普通奶牛具有完全相同的基因。

 输出格式

打印一个最短的子串,子串的要求:有斑点的牛这部分的子串,不能和无斑点的牛的这部分子串相同。求最短子串长度

输入输出样例

输入 #1

3 8
AATCCCAT
ACTTGCAA
GGTCGCAA
ACTCCCAG
ACTCGCAT
ACTTCCAT
​

输出 #1

4

题意翻译:

        一句话题意:给定 n 个A串和 n 个B串,长度均为 m,求一个最短的区间 [l,r],使得不存在一个A串a和一个B串b,使得 a[l,r]=b[l,r]并且r-l+1最小。

        说人话就是要再没有斑点的牛中任意选一个他的基因的区间,要求这个区间不等于所有的有斑点的牛的这个区间。然后求这个区间的最短长度

解析:

       

图解样例:


可以发现选择2-5这个区间的基因可以使每个没有斑点的牛的基因都和有斑点的牛的基因不同,虽然2-6,2-7都可以但题目要求要最短所以是最短区间长度是4;

60分解法:

思路:

四层循环:第一层枚举长度(len),第二层枚举左端点(l),第三层枚举没有斑点的牛(i),第四层枚举有斑点的牛(j)。通过没有斑点的牛来判断有斑点的牛符合不符合在这个区间中不相等。

代码:

详细解释看代码:

#include<bits/stdc++.h>
using namespace std;
char a[1000][1000],b[1000][1000];//输入的有斑点的牛和无斑点的牛。
unsigned int ha[1000][1001],hb[1000][1001],cf[10001];//ha代表字符串a的hash值,hb代表字符串b的hash值,cf是乘方的简称记录着131的i次方;
unsigned int get1(int i,int l,int r){//用与取出a区间第i项[l,r]的hash值
    return ha[i][r]-ha[i][l-1]*cf[r-l+1];
}
unsigned int get2(int i,int l,int r){//用与取出b区间第i项[l,r]的hash值
    return hb[i][r]-hb[i][l-1]*cf[r-l+1];
}

int main(){
    int n,m;
    cf[0] = 1;//任何数的0次方都是1
    cin >> n >> m;//n表示两种牛的n行,m是每个牛的基因的长度 
    for(int i = 1;i <= m;i++) cf[i] = cf[i-1] *131;//计算131的乘方
    for(int i = 1;i <= n;i++){
        scanf("%s",a[i]+1);//a[i]+1可以将字符数组的首个字符位置在第一上
        for(int j = 1 ;j <= m;j++)ha[i][j] = ha[i][j-1]*131 + a[i][j];//记录hash值ha[i][j]表示第i个没有斑点的牛的前j个字符的hash值
    }
    for(int i = 1;i <= n;i++){
        scanf("%s",b[i]+1);//同上
        for(int j = 1;j <= m;j++)hb[i][j] = hb[i][j-1]*131+ b[i][j];//同上
    }
    int minn = 0x3f3f3f3f;//因为要找到最小的所以要附上一个教大值,0x3f3f3f3f = 1061109567;
    for(int len = 1;len <= m;len++){//枚举长度
        for(int l = 1;l <= m-len+1;l++){//枚举左端点,左端点可以是[1,m-len+1];
            int r=l+len-1;//在长度为len下的r的值
            bool flag=false;//flag记录有斑点的牛和无斑点的牛是否相等
            for(int i = 1;i <= n;i++){//枚举第i头无斑点的牛
                for(int j = 1;j <= n;j++){//枚举第j头有斑点的牛
                    if(get1(i,l,r) == get2(j,l,r)) flag=true;//判断在区间[l,r]间第i头无斑点的牛是否等于第j头有斑点的牛,是则flag= true
                }
            }
            if(!flag) minn = min(minn,r-l+1); //如果在区间[l,r]间第1~头无斑点的牛都不等于第1~头有斑点的牛,那么用minn记录一下最小的区间的长度的值
        }
    }
    cout << minn<<endl;//输出minn;
    return 0;
}

90分解法:

思路:

我们可以发现会有一个表示区间长度的值,区间长度大于等于这个值的区间都可以满足区间内每个没有斑点的牛和每个有斑点的牛的基因不同,区间长度小于这个值的区间不能满足区间内每个没有斑点的牛和每个有斑点的牛的基因不同,所以这个区间的长度就是最小值,也就是题目所求的值。这样我们就把原问题变成了求一个值,你会发现这不就是二分嘛!所以我们可以先用二分求出长度mid,再用三层循环得出答案,第一层循环左端点(l),第二层枚举没有斑点的牛(i),第三层枚举有斑点的牛(j)。

 代码:

详细解释看代码:

#include<bits/stdc++.h>
using namespace std;
char a[1000][1000],b[1000][1000];//输入的有斑点的牛和无斑点的牛。
unsigned int ha[1000][1000],hb[1000][1000],cf[1001];//ha代表字符串a的hash值,hb代表字符串b的hash值,cf是乘方的简称记录着131的i次方;
unsigned int get1(int i,int l,int r){return ha[i][r]-ha[i][l-1]*cf[r-l+1];}//用与取出a区间第i项[l,r]的hash值
unsigned int get2(int i,int l,int r){ return hb[i][r]- hb[i][l-1]*cf[r-l+1];}//用与取出b区间第i项[l,r]的hash值
int n,m;
bool check(int mid){//检查函数(我习惯这么写)
    for(int l = 1;l <= m-mid+1;l++){//左端点(left)从1到m-mid+1;
        bool flag = false;//flag用来记录a区间中的数与b区间中的数是否相等
        int r = l+mid-1;//右端点(right)是l+mid-1
        for(int i = 1;i <= n;i++){//枚举第i头无斑点的牛
            for(int j = 1;j <= n;j++)//枚举第j头有斑点的牛
               if(get1(i,l,r) == get2(j,l,r))flag = true;//判断在区间[l,r]间第i头无斑点的牛是否等于第j头有斑点的牛,是则flag= true
        }
        if(!flag)return true; //如果在当前区间下第i头无斑点的牛不等于第j头有斑点的牛则直接返回true,读者可以自己体会一下
    }
    return false;//第i头无斑点的牛不等于第j头有斑点的牛的可能性没有了只能返回false了
}
int main(){
    cf[0] = 1;//任何数的0次方都是1
    scanf("%d%d",&n,&m);//n表示两种牛分别有n头,m是每个牛的基因的长度 
    for(int i = 1;i <= m;i++)cf[i] = cf[i-1]*131;//计算131的乘方
    for(int i = 1;i <= n;i++){
        scanf("%s",a[i]+1);//a[i]+1可以将字符数组的首个字符位置在第一上
        for(int j = 1;j <= m;j++) ha[i][j] = ha[i][j-1]*131 + a[i][j];//记录hash值ha[i][j]表示第i个没有斑点的牛的前j个字符的hash值
    }
    for(int i = 1;i <= n;i++){
        scanf("%s",b[i]+1);//同上
        for(int j = 1;j <= m;j++) hb[i][j] = hb[i][j-1]*131 + b[i][j];//同上
    }
    int l = 1,r = m,mid;//二分三要素
    while(l < r){//当l小于r时
        mid = (l+r)/2;//mid等于二分之l加r
        if(check(mid))r = mid;//如果mid可以满足区间内每个没有斑点的牛和每个有斑点的牛的基因不同 那么r= mid看看有没有更小的
        else l = mid+1;//如果mid不满足上述条件就找一个较大的
    }
    printf("%d",r);//因为答案存在r中
    return 0;//不要忘记它
}

你会发现会有一个点超时,想要的到100分的做法怎么办呢?

100分解法:

思路:

开一个O2及以上的优化就可以100分啦;

代码(O2优化代码)

#pragma GCC optimize(2)//O2优化
#pragma GCC optimize(3)//O3优化

100分啦

洛谷号: 865206

PS

还在等什么快点关注看更多优质文章吧,还有别忘了点赞!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值