区间DP:基因拼接

博客围绕基因拼接问题展开,该问题需计算将多个DNA片段拼接成一个片段的最小和最大代价。作者起初误判为贪心问题,后经咨询得知是区间DP问题。介绍了处理环形序列的技巧,给出递推关系式,还提及类似的LeetCode题目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5(选做) 基因拼接 (100 分)
基因拼接是将不同的DNA片段连接在一起。现有n个DNA片段,从左到右排成一条直线。每次选取相邻的两个DNA片段,拼接成一条更长DNA片段。规定最后1个DNA片段和第1个DNA片段相邻。重复这个过程,直到所有的DNA片段形成一个DNA片段。每次拼接,花费的代价是所选两个DNA片段的长度之和。现在想知道,上述基因拼接,花费的最小代价和最大代价分别是多少。

输入格式:
第1行,一个整数n,表示DNA片段的个数,1≤n≤200。

第2行,n个整数Li,用空格分隔,Li表示第i个DNA片段的长度,1≤Pi≤100,1≤i≤n。

输出格式:
1行,两个整数,用空格分隔,表示所求的最小代价和最大代价。

输入样例:
在这里给出一组输入。例如:

3
1 3 2
输出样例:
在这里给出相应的输出。例如:

9 11

说来惭愧,一直以为这个题目是一个贪心就完了。看似显然的东西,实际上根本就没办法证明是贪心。我一开始的思路就是看哪一个最小,就走哪一个合并,哪一个最大就走哪一个,最后算出来最小和最大。分析一下自己的思路往那方面偏差的原因,大概是在于我,第一眼就觉得这个题目是和“合并果子是一类的题目”。自以为掌握了题目精髓?
但是,咨询别的大佬之后,才知道这是一个区间DP问题。
设置一个dp[i][j]表示区间的最小代价或者最大代价,然后有个不错的技巧学到了,把序列1 2 3 4 5变成1 2 3 4 5 1 2 3 4就可以模拟所有环的情况了。避免了很复杂的环的处理。然后,这个代码并不是一次就成功的,因为递推关系式子并没有写好。
正确的递推关系式是这样的:
s u m sum sum表示前缀和,所以现在看来,突然发现我代码写的不太好,下面是第一版的代码了,我要更新一个第二版出来。
D P [ i ] [ j ] = m a x i ≤ k < j    { D P [ i ] [ k ] + D P [ k + 1 ] [ j ] + s u m [ j ] − s u m [ i − 1 ] } DP[i][j]=max_{i\leq k< j \,\,}\{DP[i][k]+DP[k+1][j]+sum[j]-sum[i-1]\} DP[i][j]=maxik<j{DP[i][k]+DP[k+1][j]+sum[j]sum[i1]}

#include<bits/stdc++.h>
using namespace std;
int f[410][410];
int input[1000];
int sum[210];
int n;
int main(){
    scanf("%d",&n);
    if(n==1){
        scanf("%d",&n);
        printf("0 0");
        return 0;
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&input[i]);
    for(int j=n+1;j<=2*n-1;j++)
        input[j]=input[j-n];
    for(int i=1;i<=2*n-1;i++)
        sum[i]=sum[i-1]+input[i];
    for(int i=2*n-1;i>=1;i--)
    {
        f[i][i]=input[i];
        for(int j=i+1;j<=i+n-1&&j<=2*n-1;j++)
        {
            if(j==i+1)
            {
                f[i][j] =sum[j]-sum[i-1];
            }
            else
            {
                int Min=INT_MAX;
                for(int k = i; k < j; k++)
                {
                    int temp_Min=sum[j]-sum[i-1];
                    if(i!=k) temp_Min+=f[i][k];
                    if((k+1)!=j) temp_Min+=f[k+1][j];
                    Min = min(Min,temp_Min);
                }
                f[i][j] = Min;
            }
        }
    }
    int Min=INT_MAX;
    for(int i=1;i<=n;i++){
        Min=min(Min, f[i][i+n-1]);
    }
    printf("%d ",Min);

    for(int i=2*n-1;i>=0;i--)
    {
        f[i][i] = input[i];
        for (int j=i+1;j<=2*n-2;j++)
        {
            if(j==i+1)
            {
                f[i][j] =sum[j]-sum[i-1];
            }
            else
            {
                int Max=INT_MIN;
                for(int k = i; k < j; k++)
                {
                    int temp_Max=sum[j]-sum[i-1];
                    if(i!=k) temp_Max+=f[i][k];
                    if((k+1)!=j) temp_Max+=f[k+1][j];
                    Max = max(Max, temp_Max);
                }
                f[i][j] = Max;
            }
        }
    }
    int Max=INT_MIN;
    for(int i=1;i<=n;i++){
        Max=max(Max, f[i][i+n-1]);
    }
    printf("%d",Max);
    return 0;
}

第二版的代码出炉:
之前改动的地方只有一个,那就是这个f[i][i]到底是等于多少,实际上就应该是0,这样的话特判都可以去掉了,还是自己题意理解不到位,竟然现在才发现这个问题。果然反思还是有很大作用的(思考.jpg)。
emmm其实这个类型的题目我也做过,LeetCode叫做奇怪的打印机
https://leetcode-cn.com/problems/strange-printer/
可惜还是整合不到一起去。这个是硬伤啊,这类型的题目看来还是有很多可以探索的。所以,还差的远呢。

#include<bits/stdc++.h>
using namespace std;
int f[410][410];
int input[1000];
int sum[210];
int n;
int main(){
    scanf("%d",&n);

    for(int i=1;i<=n;i++)
        scanf("%d",&input[i]);
    for(int j=n+1;j<=2*n-1;j++)
        input[j]=input[j-n];
    for(int i=1;i<=2*n-1;i++)
        sum[i]=sum[i-1]+input[i];

    for(int i=2*n-1;i>=1;i--)
    {
        for(int j=i+1;j<=i+n-1&&j<=2*n-1;j++)
        {
            if(j==i+1)
                f[i][j] =sum[j]-sum[i-1];
            else
            {
                int Min=INT_MAX;
                for(int k = i; k < j; k++)
                {
                    int temp_Min=f[i][k]+f[k+1][j]+sum[j]-sum[i-1];
                    Min = min(Min,temp_Min);
                }
                f[i][j] = Min;
            }
        }
    }
    int Min=INT_MAX;
    for(int i=1;i<=n;i++)
        Min=min(Min, f[i][i+n-1]);
    printf("%d ",Min);
    for(int i=2*n-1;i>=0;i--)
    {
        for (int j=i+1;j<=2*n-2;j++)
        {
            if(j==i+1)
                f[i][j] =sum[j]-sum[i-1];
            else
            {
                int Max=INT_MIN;
                for(int k = i; k < j; k++)
                {
                    int temp_Max=f[i][k]+f[k+1][j]+sum[j]-sum[i-1];
                    Max = max(Max, temp_Max);
                }
                f[i][j] = Max;
            }
        }
    }
    int Max=INT_MIN;
    for(int i=1;i<=n;i++)
        Max=max(Max, f[i][i+n-1]);
    printf("%d",Max);
    return 0;
}
随着大量的基因组DNA序列数据被获得,它对了解基因越来越重要(基因组DNA的一部分,是负责合成蛋白质的)。总所周知,在基因组序列中,由于存在垃圾的DNA中断基因的编码区,真核生物(相对于原核生物)的基因链更加复杂。也就是说,一个基因被分成几个编码片段(称为外显子)。虽然在蛋白质的合成过程中,外显子的顺序是固定的,但是外显子的数量和长度可以是任意的。 大多数基因识别算法分为两步:第一步,寻找可能的外显子;第二步,通过寻找一条拥有尽可能多的外显子的基因链,尽可能大地拼接一个基因。这条链必须遵循外显子出现在基因组序列中的顺序。外显子i在外显子j的前面的条件是i的末尾必须在j开头的前面。 本题目的目标是,给定一组可能的外显子,找出一条拥有尽可能多的外显子链,拼接一个基因输入: 给出几组输入实例。每个实例的开头是基因组序列中可能的外显子数n(0<n<1000).接着的n行,每行一对整数,表示外显子在基因序列中的起始和结束位置。假设基因组序列最长为50000.当一行是0时,表示输入结束。 输出: 对于每个实例,找出最可能多的外显子链,输出链中的外显子,并占一行。假如有多条链,但外显子数相同,那么输出其中任意一条。 输入样列: 6 340 500 220 470 100 300 880 943 525 556 612 776 3 705 773 124 337 453 665 0 输出样列: 3 1 5 6 4 2 3 1 提示:可以用贪心或动归来做。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值