2023深圳CCPC G - 相似基因序列问题(补题)

【题目背景】

生物的遗传物质存在个体间或种群水平的差异,这样的差异被称为遗传变异。突变及基因重组等因素都会导致遗传变异。尽管亲代在将其遗传信息传递给子代时会发生遗传变异,但是这些遗传变异仅占遗传物质的一小部分,通常亲代和子代之间的遗传物质非常相似。遗传变异会在生物繁殖的过程中不断累积。通过比较不同生物的基因特征及基因组结构,可以大致确定生物之间的亲缘关系,并建立系统进化树。在比较过程中,可能有一些遗传物质的子序列完全相同或相似,我们称这种序列为保守序列。

假设现在已经测定了若干以 DNA 为遗传物质的生物的 DNA 碱基序列,希望通过比较这些基序列推测生物之间的亲缘关系。为了简化比较,先将碱基序列划分为若干个保守序列片段。考虑到 DNA 序列可能发生缺失、插入等影响片段数量的遗传变异,将划分得到的片段对齐至 M个片段,并使用小写字母来表示对齐后的每一个片段。

【题目描述】

已知一棵包含了 N 个生物的系统进化树,这些生物的 DNA 序列对应的对齐至 M 个片段的序列可以用仅含小写字母的字符串表示为 s1,…,sn。在这棵系统进化树上,如果两个生物对应的序列最多只有 K 处对应位置上的片段不相同(即对应字母不同),就称这两个生物的亲缘关系相近。

现有 Q 个尚未确定亲缘关系的生物,对齐得到序列分别为 t1,…,tn。为了确定这些生物在系统进化树上的位置,请对 Q 个生物分别求出,原树中有多少个生物与其亲缘关系相近。

Input

输入的第一行包含四个正整数 N,Q,M,K,分别表示系统进化树上的生物数量、待确定亲缘关系的生物数量、对齐后的序列长度和比较序列时容许的最大差异数。保证 1≤N,Q≤300,1≤M≤60,000,1≤K≤10。

接下来 N 行,每行输入一个长度恰好为 M,仅包含小写字母的字符串 si,表示系统进化树上的每个生物对应的模板序列。

接下来 Q 行,每行输入一个长度恰好为 M,仅包含小写字母的字符串 ti,表示待确定亲缘关系的每个生物对应的查询序列。

保证输入的两个字符串均仅包含小写字母。

Output

输出共 Q 行,其中第 i 行输出一个非负整数,表示在系统进化树上与第 i 个待确定的生物亲缘关系相近的生物数量。

Sample

#0
Input
6 4 4 1
kaki
kika
manu
nana
tepu
tero
kaka
mana
teri
anan
Output
2
2
1
0
#1
Input
8 6 7 3
delphis
aduncus
peronii
plumbea
clymene
hectori
griseus
electra
delphis
helpiii
perphii
clumeee
eleelea
ddlpcus
Output
1
1
2
2
1
2

思路

k最多只有10个,所以从k入手,那么应该尽可能减少遍历数量。减少方式:类似于倍增的二分跳转。如何运用二分:用哈希值来算。

#include<bits/stdc++.h>
using namespace std;
#define enl , writestr("\n")
#define int long long
#define endl "\n"
#define PII pair<int, int>
#define ULL unsigned long long
#define xx first
#define yy second
#define PI acos(-1)
const int P = 131;//131,13331
const int N = 5e6 + 10;
const int M = 1e3 + 10;
const int INF = 1e18;
const int ENF = -1e18;
const int mod = 1e9 + 7;//998244353
const double feps = 1e-6;
void ClearFloat() { ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0); }int read() { int ret = 0, f = 1; char ch = getchar(); while ('0' > ch || ch > '9') { if (ch == '-')f = -1; ch = getchar(); }while ('0' <= ch && ch <= '9') { ret = ret * 10 + ch - '0'; ch = getchar(); }return ret * f; }int gcd(int a, int b) { if (b) while ((a %= b) && (b %= a)); return a + b; }int lcm(int a, int b) { return a * b / gcd(a, b); }int lowbit(int x) { return x & (-x); }
int T, n, m, h[310][M * 100], h1[N], q, k, p[N], ans;
string s;
int find(int i, int l, int r)//返回已知的字符串哈希
{
    return h[i][r] - h[i][l - 1] * p[r - l + 1];
}
int find1(int l, int r)//返回被询问的字符串哈希
{
    return h1[r] - h1[l - 1] * p[r - l + 1];
}
bool check(int i, int l, int r)//比较从l到r区间的字符串是否相同
{
    return find(i, l, r) == find1(l, r);
}
void slove()
{
    cin >> n >> q >> m >> k;
    p[0] = 1;
    for (int i = 1; i <= N / 10; i++) p[i] = p[i - 1] * P;
    for (int i = 0; i < n; i++)
    {
        cin >> s;
        for (int j = 1; j <= s.size(); j++)
        {
            h[i][j] = h[i][j - 1] * P + s[j - 1];
        }
    }
    while (q--)
    {
        cin >> s; int res = 0;
        for (int i = 1; i <= s.size(); i++) h1[i] = h1[i - 1] * P + s[i - 1];
        for (int i = 0; i < n; i++)
        {
            int now = 0;
            for (int j = 1; j <= m; j++)
            {
                if (!check(i, j, j))
                {
                    now++;
                    if (now > k) break;
                }
                int l = j, r = m;
                while (l < r)//减少遍历方式
                {
                    int mid = l + r + 1 >> 1;
                    if (check(i, l, r)) l = mid;//如果该区间内没有不同,那么l跳转至mid
                    else r = mid - 1;//否则r向前跳转
                }
                j = l;//另j=l,减少遍历数量
            }
            if (now <= k) res++;
        }
        cout << res << endl;
    }
}  
signed main()
{
    ClearFloat();
    T = 1;
    //cin >> T;
    for (int i = 1; i <= T; i++)
    {
        slove();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值