SDAU训练日志第13篇----------动态规划(9)(2018年2月10日)

放上几个今天碰见不错的题。

洛谷P1140 基因相似问题(并没有做出来,参考题解)

题目背景

大家都知道,基因可以看作一个碱基对序列。它包含了4种核苷酸,简记作A,C,G,T。生物学家正致力于寻找人类基因的功能,以利用于诊断疾病和发明药物。

在一个人类基因工作组的任务中,生物学家研究的是:两个基因的相似程度。因为这个研究对疾病的治疗有着非同寻常的作用。

题目描述

两个基因的相似度的计算方法如下:

对于两个已知基因,例如AGTGATG和GTTAG,将它们的碱基互相对应。当然,中间可以加入一些空碱基-,例如:

这样,两个基因之间的相似度就可以用碱基之间相似度的总和来描述,碱基之间的相似度如下表所示:

那么相似度就是:(-3)+5+5+(-2)+(-3)+5+(-3)+5=9。因为两个基因的对应方法不唯一,例如又有:

相似度为:(-3)+5+5+(-2)+5+(-1)+5=14。规定两个基因的相似度为所有对应方法中,相似度最大的那个。

输入输出格式

输入格式: 共两行。每行首先是一个整数,表示基因的长度;隔一个空格后是一个基因序列,序列中只含A,C,G,T四个字母。1<=序列的长度<=100。
输出格式:

仅一行,即输入基因的相似度。

#include<iostream>
using namespace std;
int n,m;
string a,b;
int f[101][101];
int s1[101],s2[101];
int gene[5][5]=
{{5,-1,-2,-1,-3},
 {-1,5,-3,-2,-4},
 {-2,-3,5,-2,-2},
 {-1,-2,-2,5,-1},
 {-3,-4,-2,-1,0}};
int main()
{
    cin>>n>>a>>m>>b;
    for(int i=1;i<=n;i++)
      {
          if(a[i-1]=='C') s1[i]=1;
          if(a[i-1]=='G') s1[i]=2;
          if(a[i-1]=='T') s1[i]=3;
      }
    for(int i=1;i<=m;i++)
      {
          if(b[i-1]=='C') s2[i]=1;
          if(b[i-1]=='G') s2[i]=2;
          if(b[i-1]=='T') s2[i]=3;
      }
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
        f[i][j]=-999999; 
    for(int i=1;i<=n;i++)
      f[i][0]=f[i-1][0]+gene[s1[i]][4];
    for(int i=1;i<=m;i++)
      f[0][i]=f[0][i-1]+gene[s2[i]][4];
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i][j],f[i][j-1]+gene[s2[j]][4]);
            f[i][j]=max(f[i][j],f[i-1][j]+gene[s1[i]][4]);
            f[i][j]=max(f[i][j],f[i-1][j-1]+gene[s1[i]][s2[j]]);
        }
    cout<<f[n][m];
    return 0;
}

1.用string存储输入并转化为代表基因的数字编码

2.初始化时把相似度赋一个很小的非法值防止出现错误

3.注意边界情况

  for(int i=1;i<=n;i++)
      f[i][0]=f[i-1][0]+gene[s1[i]][4];
    for(int i=1;i<=m;i++)
      f[0][i]=f[0][i-1]+gene[s2[i]][4];

3.注意这三个状态转移方程(注意状态转移方程的构建方法)

            f[i][j]=max(f[i][j],f[i][j-1]+gene[s2[j]][4]);//第二个基因加空碱基
            f[i][j]=max(f[i][j],f[i-1][j]+gene[s1[i]][4]);//第一个基因加空碱基
            f[i][j]=max(f[i][j],f[i-1][j-1]+gene[s1[i]][s2[j]]);//直接配对

答案是三个状态转移方程中的最优解


P1130红牌

题目描述

某地临时居民想获得长期居住权就必须申请拿到红牌。获得红牌的过程是相当复杂 ,一共包括N个步骤。每一步骤都由政府的某个工作人员负责检查你所提交的材料是否符合条件。为了加快进程,每一步政府都派了M个工作人员来检查材料。不幸的是,并不是每一个工作人员效率都很高。尽管如此,为了体现“公开政府”的政策,政府部门把每一个工作人员的处理一个申请所花天数都对外界公开。

为了防止所有申请人都到效率高的工作人员去申请。这M*N个工作人员被分成M个小组。每一组在每一步都有一个工作人员。申请人可以选择任意一个小组也可以更换小组。但是更换小组是很严格的,一定要相邻两个步骤之间来更换,而不能在某一步骤已经开始但还没结束的时候提出更换,并且也只能从原来的小组I更换到小组I+1,当然从小组M可以更换到小组1。对更换小组的次数没有限制。

输入格式:

输入文件red.in的第一行是两个正整数N和M,表示步数和小组数。接下来有M行,每行N个非负整数,第i+1(1<=i<=M)行的第j个数表示小组i完成第j步所花的天数,天数都不超过1000000。

输出格式:

输入文件red.out仅包括1个正整数,为完成所有步所需最少天数。。

#include<bits/stdc++.h>
using namespace std;
int dp[2002][2002],a[2002][2002];
int main()
{
    int r=0;//r表示步数
    int m=0;//小组数
    cin>>r>>m;
    for(int i=1;i<=m;i++)
        for(int j=1;j<=r;j++)
            cin>>a[j][i];
    for(int i=1;i<=m;i++)
        dp[r][i]=a[r][i];
    for(int i=r-1;i>=1;i--)
        for(int j=1;j<=m;j++)
            if(j==m) dp[i][j]=min(dp[i+1][1],dp[i+1][j])+a[i][j];
            else dp[i][j]=min(dp[i+1][j+1],dp[i+1][j])+a[i][j];
    int minn=dp[1][1];
    for(int j=2;j<=m;j++)
        minn=min(minn,dp[1][j]);
    cout<<minn<<endl;
}

1.想了一下其实就是数塔问题的升级版(状态转移方程都一模一样),不过加了一个对于边界j=m时的特殊判定,(小组m可以转换成小组1),用自底向上的递推方法做出来了,自顶向下的记忆化搜索应该也可以,不过我还是喜欢自底向上,感觉好理解点。。改天用记忆化搜索试试看。

2.一定要细心,题目的输入是竖着给的,,要不是画了个图差点被坑死。

3.作为数塔的升级版还是和数塔有区别的,如果是数塔的话出口只有最顶上的一个出口,也就是答案是dp[1][1];

然而这个题出口有四个,出口选最优解。即:

 int minn=dp[1][1];
    for(int j=2;j<=m;j++)
        minn=min(minn,dp[1][j]);
    cout<<minn<<endl;

P1091合唱队形

题目描述

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式:

输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。

输出格式:

输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。


#include<iostream>
using namespace std;
int max(int x,int y){return x>y?x:y;}
int main()
{
    int high[105],remain_left[105],remain_right[105];
    int n=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>high[i];
        remain_left[i]=1;
        remain_right[i]=1;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<i;j++)
            if(high[i]>high[j])
                remain_left[i]=max(remain_left[i],remain_left[j]+1);
    for(int i=n;i>=1;i--)
        for(int j=n;j>i;j--)
            if(high[i]>high[j])
                remain_right[i]=max(remain_right[i],remain_right[j]+1);
    int out_put=0;
    for(int i=1;i<=n;i++)
        out_put=max(out_put,(remain_left[i]+remain_right[i]-1));
    cout<<n-out_put<<endl;
}

1.由题意可以看出从1...Ti 单调上升  Ti..n单调下降。

所以思路为1...Ti 求一次最长上升子序列remain_left,再从Ti倒着求一次最长上升子序列。remain_right

2.求出两个LIS后,remain_left+remain_right-1就是总共合乎要求的人数!!!(千万别忘了-1(这里参考了答案))

3.从1...n进行遍历,寻找remain_left+remain_right-1的最大值,即为能留下的最大人数。

4.用总人数减去上面的留下的人数就是答案。。。(别算完最大值之后忘了减直接输出了,,,(比如我))


不写了,睡觉去。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值