东方博宜OJ 1529:基因锁 ← KMP算法 + 区间点覆盖 + 贪心

【题目来源】
https://oj.czos.cn/p/1529

【题目描述】
……
人类的基因序列(英文缩写为 DNA)是一个由字母 'A','C','G','T' 组成的字符串,肥胖基因是其中的一个子串(子串为原串中一段连续的字符)。
外星人对小 X 的基因手术过程是这样的:先找出所有的肥胖基因,并将它们用基因墨水染成红色,然后将某些字符加上基因锁,一把基因锁只能锁住一个字符,一个肥胖基因只要有一个字符加上了基因锁,则这个肥胖基因就不再起作用。
现在要你计算有多少个字符被基因墨水染成了红色?最少需要多少把基因锁才能将所有的肥胖基因锁住?

【输入格式】
第一行包含两个用空格隔开的正整数 L1,L2,表示小 X 基因的长度和肥胖基因的长度。
第二行为一个长度为 L1 的字符串,表示小 X 的基因。
第三行为一个长度为 L2 的字符串,表示肥胖基因。数据保证 L1>L2。

【输出格式】
输出一行包含两个整数,表示被基因墨水染成了红色的字符个数和所需的基因锁数量,两数之间严格用一个空格隔开。

【输入样例】
16 3
CGCGCATCGCATTAGG
CGC

【输出样例】
8 2

【数据范围】
10% 的数据,肥胖基因为单个字符;
40% 的数据,所有的肥胖基因互相不重叠;
100% 的数据,基因长度不超过 10^6,肥胖基因长度不超过 10,保证字符只会出现 'A','C','G',T'。

【算法分析】
● 这道题两个问题,分开解决:
(1)染红字符数:所有被匹配到的模式串覆盖的不同位置总数。
(2)最少基因锁:经典区间点覆盖问题,用最少的点戳中所有区间(贪心)。

● 代码里用的贪心策略是“每次在当前区间的最右端放锁”。为什么?因为靠右放,能最大概率覆盖后面可能重叠的区间,从而用最少数量的锁覆盖所有区间,保证得到最优解。
例如,对于字符串 ACBCBCBC 与模式串 CBC,匹配得到三个首尾相接的区间 [1,3]、[3,5]、[5,7] ,采用 “在当前区间最右端放锁” 的贪心策略,是因为将锁放在区间右端点能尽可能覆盖后续与之重叠的区间:第一个区间 [1,3],在右端点 放锁,可同时覆盖自身与下一个区间 [3,5],无需额外加锁;待遍历到未被覆盖的第三个区间 [5,7] 时,再在其右端点 放锁即可覆盖全部区间,仅用2把锁就完成全部锁定。若选择在区间左侧或中间位置放锁,会无法覆盖后续相邻区间,导致需要更多锁,而靠右放锁能最大程度利用区间重叠,用最少数量的锁覆盖所有区间,这也是该贪心策略能得到最优解的关键原因。

【算法代码】

#include <bits/stdc++.h>
using namespace std;

const int N=1e6+5;
int ne[N];
bool st[N];

void getNext(string t) {
    int len=t.length();
    int i=0,j=-1;
    ne[0]=-1;
    while(i<len) {
        if(j==-1 || t[i]==t[j]) {
            i++,j++;
            ne[i]=j;
        } else j=ne[j];
    }
}

vector<int> KMP(string s,string t) {
    int lens=s.length(),lent=t.length();
    int i=0,j=0;
    vector<int> pos;
    while(i<lens && j<lent) {
        if(j==-1 || s[i]==t[j]) {
            i++,j++;
        } else j=ne[j];
        if(j==lent) {
            pos.push_back(i-j+1);
            j=ne[j];
        }
    }
    return pos;
}

int main() {
    int L1,L2;
    string s,t;
    cin>>L1>>L2>>s>>t;
    getNext(t);
    vector<int> pos=KMP(s,t);

    //Locations that are covered
    memset(st,0,sizeof st);
    for(int i=0; i<pos.size(); i++) {
        int p=pos[i];
        for(int j=0; j<L2; j++) {
            st[p+j]=true;
        }
    }

    int cnt=0;
    for(int i=0; i<L1; i++) {
        if(st[i]) cnt++;
    }

    //min cnt that covers all intervals
    int ans=0;
    int last=-1;
    for(int i=0; i<pos.size(); i++) {
        int p=pos[i];
        int x=p+L2-1;
        if(p>last) {
            ans++;
            last=x;
        }
    }
    cout<<cnt<<" "<<ans<<endl;

    return 0;
}

/*
in:
16 3
CGCGCATCGCATTAGG
CGC

out:
8 2
*/





【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/160215718
https://blog.csdn.net/hnjzsyjyj/article/details/160234350
https://blog.csdn.net/hnjzsyjyj/article/details/160228242
https://blog.csdn.net/hnjzsyjyj/article/details/160224665
 

posted @ 2026-04-17 13:55  Triwa  阅读(7)  评论(0)    收藏  举报