杭电60道DP问题总结(一)

杭电60道DP问题总结:


DP是一个很有艺术的思想。看似简单的背后却隐藏着深刻的含义。

题目连接地址:http://acm.hdu.edu.cn/problemclass.php?id=516&page=1

        

学习算法最好的办法就是不断的练习。

        本来想等把全部的DP问题做完了再发一个总帖,但是发现还是会有忘记,断断续续的做了一些,还是先总结一部分,之后的等考完试了再补充回来。

所有题目都AC过了,有自己写的,也有从网上参考学习的,拿出来与大家交流分享,我个人对某些题目的理解可能有误,如果有的话,还希望大家批评指正。

        言归正传,一道一道题目来。


ProID  1003  Max Sum

题目意思是,给定一个数组,然后求这个数组中最大的连续子串之和,输出不仅需要输出最大值,还需要输出这个范围。

        这算是最简单的DP问题之一了。

设 f[x] 为以 a[x]终止包含 a[x] 的最大序列的和,有:f[1] = a[1];
  f[x+1] = f[x] > 0 ? f[x] + a[x+1] : a[x+1],那么最大子序列的和就是 f[1] .. f[n] 中最大的一个。

这里一定要注意是以a[x]结尾的,就是所谓的无后效性,开始学习的时候不明白为什么一定要以a[x]结尾并且包含a[x]呢,注意DP是把大问题的规模缩小,现在我们假设f[x]为仅仅以a[x]结尾但不包括a[x]的最大子串之和,那么扩大问题的公式(就是所谓的递归方程式或者状态转移方程)如何写呢?

                f[x+1]=?无法推导了,因为当考虑第x+1元素时,题中要求是连续子串,f[x]表示的前x个中的子串,可不一定包括第x个,虽然无后效性了,但是无法从前边的状态推导过来,即只能解决规模为1的问题。

一定记住,状态转移方程是从当前状态转到下一个状态时,增大问题规模,但是一定是与前一个状态而来的,切记。

回到问题上来,就可以理解为什么以a[x]结尾并且包含a[x]了。

 至于找到区间就简单了,首先在结果集中找到最大值,注意f[x]表示包含第x个元素,但是最终的结果可不一定以最后一个元素结尾,所以还需要对f数组搜索,找到最大值,然后记录下最大值的位置,向前依次减去元素,等到结果为0时在记录下此时的下表,这样就出结果了。

参考代码如下:

#include<cstdio>
#include<cstring>
#include<cstdlib>
int a[100001];
int dp[100001];
int main()
{
    int T;
    scanf("%d",&T);
    
        for(int i=0;i<T;i++)
        {
            int n;
            scanf("%d",&n);
            /*
            设 f[x] 为以 a[x] 终止且包含 a[x] 的最大序列的和,有:f[1] = a[1];
   f[x+1] = f[x] > 0 ? f[x] + a[x+1] : a[x+1]
那么最大子序列的和就是 f[1] .. f[n] 中最大的一个。
            */
            memset(dp,0,sizeof(dp));
            for(int j=0;j<n;j++)
                scanf("%d",&a[j]);
            dp[0]=a[0];
            for(int k=1;k<n;k++)
            {
                if(dp[k-1]>0)
                    dp[k]=dp[k-1]+a[k];
                else
                    dp[k]=a[k];
            }
            int start=0;
            int max=dp[0];
            for(int k=1;k<n;k++)
            {
                if(max<dp[k])
                {
                    start=k;
                    max=dp[k];
                }
            }
            int end=start;
            for(int k=end-1;k>=0;k--)
            {
                if(dp[k]>=0)
                    end=k;
                else
                    break;
            }
            
            
            printf("Case %d:\n",i+1);
            printf("%d %d %d\n",max,end+1,start+1);
            if(i!=T-1)
                printf("\n");
        }


    return 0;
}

ProID  1011  Starship Troopers(这个电影倒是很不错的)

这个题目还是有点难度的,我也是在网上查了些资料才大概搞明白的,树形DP问题。

大概意思是:有N个洞,M个士兵,每个洞中有a个bug,b个brain,每个士兵可以最多Kill掉20个bug,入口在洞口1处,问可以得到多少个brain。 

这些洞是按照图的形式连接的,题中在输入数据的时候给出了图的边。不过根据题意,只有N-1条边,所以刚好是一个树,也就是所谓的树形DP,当然我是这样理解的,树形DP可不是这么简单,如果要深入理解的话,还是要去google一下资料(插一句,这方面的东西google出来的比baidu的多)。

回到题目,状态方程:value[a][b] = max(value[a][b],value[a][b - k] + value[j][k]),其中a、j之间有边相连。value[a][b]表示以a为根节点的子树,包含b个士兵,所能得到最大brain。 按a来分层进行动态规划。 这里的状态转移方程是根据与之相连的点所决定的,因为只能从当前节点走到与之相连的点。注释在代码中写的很清楚了。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Room
{
    int bugs;
    int brains;
};
Room rooms[101];
int value[101][101];
int matrix[101][101];
int visited[101];
int n,m;
void dpTree(int u)
{
    visited[u]=1;
    int r=(rooms[u].bugs+19)/20; //这个r表示当前这个房间里需要留几个士兵,即使是1个虫子也需要一个兵。
    for(int i=m;i>=r;i--)
        value[u][i]=rooms[u].brains;//只要在这个房子里留够士兵,那么就可以获得全部的brains
    for(int v=1;v<=n;v++)
    {
        if(matrix[u][v] && !visited[v])//选择当前点相邻的点,并且此边没有访问过,注意只能往下走而不能走回去
        {
            dpTree(v);
            for(int j=m;j>=r;j--)
                for(int k=1;k<=j-r;k++)
                    value[u][j]=max(value[u][j],value[u][j-k]+value[v][k]);
        }//这个状态转移方程的意思是,第u个房间里留j个士兵所获得brains是由在第u个房间里留j-k个士兵,在与之相连的第v个房间里就k个士兵所获得的brains所决定的。一定记住了,当前的状态仅仅由与其相连的点的状态所决定!!!
    }
}
int main()
{
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        if(n==-1)
            break;
        memset(rooms,0,sizeof(rooms));
        memset(matrix,0,sizeof(matrix));
        memset(visited,0,sizeof(visited));
        memset(value,0,sizeof(value));
        for(int i=1;i<=n;i++)
            scanf("%d %d",&rooms[i].bugs,&rooms[i].brains);
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d %d",&u,&v);
            matrix[u][v]=matrix[v][u]=1;
        }
        if(m==0)
        {
            printf("0\n");
            continue;
        }
        dpTree(1);
        printf("%d\n",value[1][m]);
    }
    return 0;
}


ProID  1024 Max Sum Plus Plus

题目意思是,定由n个整数(可能为负整数)组成的序列e1,e2,…,en,以及一个正整数m,要求确定序列的m个不想交子段,使得这m个子段的总和达到最大。说实话,这个题目也是搞了好久才明白过来。

如果m=1,那么就是我们上边所说的第一道题目了,不过问题可不是那么简单的。分段之后可能原来不需要的元素变得可以使用了,所以得转换一下思路。

根据一般的思路而言,状态的转移也就是增大数据规模的过程,对于本题而言,就是不断考虑下一个元素的过程(也就是顺序DP),这里可以考虑使用模糊方程:

dp[i][j] =MAX(dp[?][?],dp[??][??]) 

套公式,dp[i][j]表示前j个元素分成i段的最大和(还记得我们第一题的内容么,是一个一维数组),那么考虑下一元素(注意这里不需要一定包含第j个元素了,因为有分段的存在)。

dp[i][j]=MAX(dp[i][j-1] , dp[i-1][k])+data[j] 其中i<k<=j。

表示第j个元素可以自成一段,或者拼接到第i段的最后一位。好了,问题解决了,不过回过头来看看,方程式中仅仅用到了当前状态和前一状态,所以不用开2维数组,根据题目要求,这个空间消耗比较大,只需要2个一维数组记录当前和原始状态就可以了。具体解释在代码中。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
int curr_best[1000010];//保存当前状态
int prev_best[1000010];
int max_sum,i,j;
int n,m;
int data[1000010];
#define MIN_SUM 0x80000000
int main()
{
    while(scanf("%d %d",&m,&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%d",&data[i]);
        memset(curr_best,0,sizeof(curr_best));
        memset(prev_best,0,sizeof(prev_best));
        int j=0;
        for(int i=1;i<=m;++i)
        {
            max_sum=MIN_SUM;
            for( j=i;j<=n;++j)
            {
                //curr  b(i,*);
                //prev b(i-1,*);
		curr_best[j]=max(curr_best[j-1],prev_best[j-1])+data[j];
                prev_best[j-1]=max_sum;//这两条语句不能写反了,这块我纠结了好久,解释一下,prev_best[j-1]表示的是上一个状态中i...j-1的最大值,max_sum更新之后表示的i...j的最大值,所以不能写反了
		max_sum=max(max_sum,curr_best[j]);
            }
            prev_best[j-1]=max_sum;//prev_best[j-1]中始终保持着前一个状态的最大值,这个很重要
        }
        printf("%d\n",max_sum);
    }
    return 0;
}

ProID  1025 Constructing Roads In JGShining's Kingdom
题目意思是,两条线,每条线上各有n个点,表示n个城市,第一条表示rich 城市,第二天表示poor 城市,从第一条线连接到第二条线上,问最多能练几条,已知所有可连接的线段。

开始没搞明白,才发现就是个最长上升子序列问题。不过用一般的解法会超时,查阅资料后方得知原来需要DP+二分查找。说来惭愧,当时看明白了,可惜今天再拿出来瞅瞅又给忘了。其实最终的代码很简单,但往往是越简单的代码蕴含的思想越是复杂牛逼。下面的解释摘自Lce_Crazy的博客。

假定存在一个序列d[1...9]=2 1 5 3 6 4 8 9 7,可以看出LIS长度为5。现在开始一步一步的找出来最终的结果。

定义序列B,令i=1...9来逐个考察。此外用变量len来记录现在最长算到了多少了。

首先,把d[1]放到B里,即B[1]=d[1],就是说长度为1的LIS的最小末尾是2,这时len=1,然后把d[2]有序的放到B里,令B[1]=1(d[2]),就是说长度为1的LIS最小末尾是1,d[1]=2已经没有用了,这是len=1不变。接着d[3]=5,5>1,所以令B[1+1]=d[3]=5,就是说长度为2的LIS的最小末尾是5,此时B[1...2]=1,5,len=2。

再来,d[4]=3,它正好在1和5之间,当然放在1的位置上不合适,因为1<3,因此长度为2的最小末尾应该是3,淘汰掉5,这时候B[1...2]=1,3,len=2,继续d[5]=6,它在3后边,所以B[3]=4,B[1...3]=1,3,6,len=3。

第6个,d[6]=4,它加在3和6之间,所以我们把6换掉,这样B[1...3]=1,3,4,len=3。

第7个,d[7]=8,8>4,所以B[4]=8,B[1...4]=1,3,4,8,len=4。

第8个,d[8]=9,B[1...5]=1,3,4,8,9,len=5。

最后一个,d[9]=7,所以B[1...5]=1,3,4,7,9,len=5。

注意这里的B并不是LIS,而是对应长度的LIS的最小末尾。

现在可以发现B插入数据是有序的,而且进行替换而不需要移动,也就是说可以使用二分查找,时间复杂度就降下来了。

PS:注意了,这种算法只能得到最终的数据个数,但是如果需要得到具体的内容就不行了。

提示一下,看网上说首先要排序,其实仔细看看题目的说明,它的边的输入是有序的,所以根本不需要进行排序。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int  num[50001];
int  ans[50001];
int main()
{
    int n,k=1;
    int len,low,heigh,mid;
    int tmp1,tmp2;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d %d",&tmp1,&tmp2);
            num[tmp1]=tmp2;
        }
        memset(ans,0,sizeof(ans));
        ans[1]=num[1];
        len=1;
        for(int i=2;i<=n;i++)
        {
            low=1;
            heigh=len;
            while(low<=heigh)
            {
                mid=(low+heigh)/2;
                if(ans[mid]<num[i])
                    low=mid+1;
                else
                    heigh=mid-1;
            }
            ans[low]=num[i];
            if(low>len)
                len++;
        }
        printf("Case %d:\n",k);
        if(len==1)
            printf("My king, at most 1 road can be built.");
        else
            printf("My king, at most %d roads can be built.",len);        

        printf("\n\n");
        k++;
    }
    return 0;
}

ProID  1058 Humble Number

先呵呵下,这道题被各种吐槽,英文不过关啊。

题目是让找出第N个Humble Number,定义是除1外所有仅有2,3,5,7因子的数。

不多少了,这个题目或许可以用DP来解,但是貌似完全没有必要。不解释了。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int n;
typedef long long LL;
LL data[5843];

void init()
{

    int i=1,j=1,m=1,n=1;
    int index=1;
    data[index]=1;
    while(1)
    {
        if(index==5843)
            break;
        LL tmp1=min(data[i]*2,data[j]*3);
        LL tmp2=min(data[m]*5,data[n]*7);
        LL res=min(tmp1,tmp2);
        if(res== data[i]*2) i++;
        else if(res== data[j]*3) j++;
        else if(res== data[m]*5) m++;
        else if(res== data[n]*7) n++;
        
        if(res!= data[index])
            data[++index]=res;
    }
}
int main()
{
    init();
    while(scanf("%d",&n)!=EOF,n)
    {
        if(n%10==1 && n%100!=11) 
            printf("The %dst humble number is %lld.\n",n,data[n]);
        else if(n%10==2 && n%100!=12)
            printf("The %dnd humble number is %lld.\n",n,data[n]);
        else if(n%10==3 && n%100!=13)
            printf("The %drd humble number is %lld.\n",n,data[n]);
        else
            printf("The %dth humble number is %lld.\n",n,data[n]);
    }
    return 0;
}

ProID  1059 Dividing

题目意思是,有一堆钻石,有6种不同的价值(1...6),现在告诉你每种的数量,问你能不能把这堆钻石平均按总的价值平均分开。

之前有做过9度上的一道题目,是把一堆球平均分成2堆,大概算法是:

求出所有总重量<= N / 2的可能性,如F[w]=true,表示可以累积1个或若干个球得到w的总重量。最后找出最接近N/2的即可。时间复杂度O(n*N),n为球的总数量,N为球的总质量。

这里有不同的地方,首先每个球的个数不定,而且我用上述方法的时候超时了,所以考虑别的方法。但有一点是一样了,只要能够得到刚好总价值一半的值,那么就可以平均分,怎么得呢,当然是背包问题了,不过不是01背包,而是多重背包问题,只要以总价值的一半为背包重量,那么只要能装满,就可以平均分。

代码是参考背包九讲中的公式:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int data[7];
int dp[500001];
int m;

void ZeroOnePack(int value,int weight)
{
    for(int i=m;i>=value;i--)
        dp[i]=max(dp[i-value]+weight,dp[i]);
}
void CompletePack(int value,int weight)
{
    for(int i=value;i<=m;i++)
        dp[i]=max(dp[i],dp[i-value]+weight);
}
void MultiPack(int value,int weight,int amount)
{
    if(value*amount>=m)
    {
        CompletePack(value,weight);
        return ;
    }
    for(int k=1;k<amount;k*=2)
    {
        ZeroOnePack(k*value,k*weight);
        amount-=k;
    }
    ZeroOnePack(amount*value,amount*weight);
}
int main()
{
    int count=1;
    while(scanf("%d",&data[1])!=EOF)
    {

        for(int i=2;i<=6;i++)
            scanf("%d",&data[i]);
        bool out=true;
        int sum=0;
        for(int i=1;i<=6;i++)
        {
            sum+=data[i]*i;
            if(data[i])
                out=false;
        }
        if(out)
            break;
        printf("Collection #%d:\n",count++);
        if((sum&1) ==1)
        {
            printf("Can't be divided.\n");
            printf("\n");
            continue;
        }
        m=sum/2;
        int value=0;
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=6;i++)
            if(data[i])
                MultiPack(i,i,data[i]);
            if(dp[m]==m)
                printf("Can be divided.\n");
            else
                printf("Can't be divided.\n");
            printf("\n");
    }
    return 0;
}

ProID  1069  Monkey and Banana

题目大意是,一只猴子,想吃香蕉,给他一堆垫子,他把垫子堆起来自己站上去好能够到香蕉,堆垫子的规则是下边的面积一定要大于上边的面积。

这个题目依然是最长上升子序列问题,只不过比较的规则是面积而已。

PS:每种垫子可以使用多个,垫子有长宽高,可以以多种样子摆放。

不多解释了,代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct _data
{
    int x,y,z,area;
};
_data data[200];

bool com(_data d1,_data d2)
{
    return d1.area<d2.area;
}
int n;
int dp[200];
int main()
{
    int count=1;
    while(scanf("%d",&n)!=EOF&&n)
    {
        int index=0;
        for(int i=0;i<n;i++)
        {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            data[index].x=x;data[index].y=y;data[index].z=z;data[index++].area=x*y;
            data[index].x=y;data[index].y=x;data[index].z=z;data[index++].area=x*y;
            data[index].x=y;data[index].y=z;data[index].z=x;data[index++].area=z*y;
            data[index].x=z;data[index].y=y;data[index].z=x;data[index++].area=z*y;
            data[index].x=x;data[index].y=z;data[index].z=y;data[index++].area=x*z;
            data[index].x=z;data[index].y=x;data[index].z=y;data[index++].area=x*z;
        }
        sort(data,data+index,com);
        memset(dp,0,sizeof(dp));
        dp[0]=data[0].z;
        for(int i=1;i<index;i++)
        {
            int temp=0;
            for(int j=0;j<i;j++)
                if(data[i].x>data[j].x && data[i].y>data[j].y&&temp<dp[j])
                    temp=dp[j];
                dp[i]=temp+data[i].z;
        }
        int ans=dp[0];
            for(int i=1;i<index;i++)
                ans=max(ans,dp[i]);
        printf("Case %d: maximum height = %d\n",count++,ans); 
    }
    return 0;
}


ProID  1074 Doing Homework

题目大意是,给定一些课程,包括截止期和扣分,问怎样编排课程是得扣分最少。

看了半天没有思路,只好google,用的是所谓的压缩DP,用一位表示一个状态,也算是个好题,至少学到了些基本的方法。

下面的大部分东西摘自GentleH的博客,代码里我增加了注释,应该还是比较容易理解的。

 状态压缩DP 

因为只有15个课程,用15位的二进制表示课程的完成状态,1表示完成,0表示未完

假定有4门课程,1100表示的是第4门和第3门课是完成的,也就是说可以用12这个十进制数来表示完成了第4和第3门课这个状态。那么15门课程,如果表示全部都完成了,那么只需用32767来表示。

    二进制操作: 
(1)i & j:j为第j门课,则这个操作表示的是i这个状态是否包含了j,也就是得到i和j的相同之处。比如1011 & 0010 = 0010(!=0),说明i是包含j的。相同部分就是j。
   
(2)i ^ j: 对于i和j两个不同的状态,该操作的结果是得到i和j的不同之处。比如1011 ^ 0010 = 1001。说明了i这个状态除了j之外,还有第4门和第1门是完成的。这跟&操作恰好相反。
   
    PS:  i | j:将i和j两个状态合并,比如1011 & 0100 = 1111;



状态方程比较复杂,我解释一下。

dp[i]中有3个字段,pre,res,cost,分别表示上一个状态,需要扣的分,以及达到当前状态需要花费的时间。

这个下表i范围是0...1<<n,用二进制的每一位表示一个作业。

在当前状态i中,如果包含了课程cur,那么这个状态就是由这个课程转换而来的,规则是

状态i中的cost=状态 i^cur(出cur课程以外所有课程)的cost+完成课程cur所需要的时间。

如果到达状态i时所花费的总时间不超过课程cur的deadline,那么就不会增加扣分。否则需要记录这个扣分,咋那么总的扣分是状态i^cur的扣分+完成当前课程所扣的分(有就加,没有就不加)。现在需要确定是否状态转换,是否转换时候扣的分的多少而决定的。如果当前状态的扣分>由cur转换过来的扣分,那么就更新状态,即当前状态是由cur转换过来的。

代码如下:

#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;

struct Work
{
    char name[105];
    int deadline;
    int cost;
}work[20];

struct DP
{
    int pre;
    int res;
    int cost;
}dp[40005];
int N;

void out(int state)
{
    if(state>0)
    {
        out(dp[state].pre);
        int t=dp[state].pre;
        int ans=t ^ state;//找出当前状态和上一状态的不同之处
        //也就是当前的课程
        int idx=0;
        //找出该十进制数在二进制表示下,1出现在什么位置,因为1代表完成了哪个课程。
        while(ans>0)
        {
            idx++;
            ans>>=1;
        }
        printf("%s\n",work[idx].name);
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&N);
        for(int i=1;i<=N;i++)
            scanf("%s %d %d",&work[i].name,&work[i].deadline,&work[i].cost);
        int Max=(1<<N )-1;
        dp[0].cost=0;
        dp[0].pre=-1;
        dp[0].res=0;

        for(int i=1;i<=Max;i++)
        {
            dp[i].cost=0;
            dp[i].pre=-1;
            dp[i].res=0xfffffff;
            for(int j=0;j<N;j++)
            {
                int cur=1<<j;
                if(i & cur) //包含了该课程
                {//状态,pre是上一个状态,res是需要扣的分,cost是达到当前状态需要花费的时间
                    dp[i].cost=dp[i^cur].cost+work[j+1].cost;
                    int reduce=dp[i].cost-work[j+1].deadline;
                    if(reduce<0)
                        reduce=0;
                    reduce += dp[i^cur].res;
                    //因为题目中的课程输入已经保证是字典序,所以==的时候一样更新
                    if(dp[i].res>=reduce)
                    {
                        dp[i].res=reduce;
                        dp[i].pre=i^cur;
                    }
                }
            }
        }
        printf("%d\n",dp[Max].res);
        out(Max);
    }
    return 0;
}


ProID 1078 FatMouse and Chese
题目大意是,给你一个二维数组,从0,0开始,找到一条最长的路,从(x1,y1) ->(x2,y2)

要满足 x1==x2&& |y1-y2|<=k 或者 y1==y2&&|x1-x2|<=k。


PS:一定注意啊,是上下都可以走,左右可也走,最开始我以为是只能向下或者向右走,郁闷了半天。

对于路径问题,一般都是深度搜索,所以我们也用DFS方法。


解释一下状态转移方程,dp[i][j]表示以第i行第j列个网格为起点所能得到的最大值,注意了因为是深度搜索,是不断向下走的,所以状态的转移是以下边的状态为基础的,而不是以前边的状态为基础,因此就不是这个意思:dp[i][j]表示从0,0到i,j的最大值,这是绝对错误的。


状态转移方程为:dp[i][j] = max{dp[i + d1*c][j + d2*c], 1=< c <= k} + map[i][j]。


代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int data[101][101];
int dp[101][101];//dp[i][j]表示从位置(i,j)为起点时能吃到最多的

int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};

int n,k;

int Dfs(int x,int y)
{
    if(dp[x][y]>0)//如果已经有值了,可以直接返回
        return dp[x][y];
    int Max=0;
    for(int i=0;i<4;i++)//搜四个方向
        for(int j=1;j<=k;j++)//一次可以跳1至k步
        {
            int tx=x+dir[i][0]*j;
            int ty=y+dir[i][1]*j;
	    //必须跳的位置的奶酪比当前所站的位置多
            if(tx>=0 && tx<n && ty>=0 && ty<n && data[x][y]<data[tx][ty])
            {
                int temp=Dfs(tx,ty);
                Max=max(Max,temp);
            }
        }
        
    dp[x][y]=Max+data[x][y];
    return dp[x][y];
}

int main()
{
    while(scanf("%d %d",&n,&k)!=EOF)
    {
        if(n==-1 && k==-1)
            break;
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&data[i][j]);
        memset(dp,0,sizeof(dp));
        Dfs(0,0);
        printf("%d\n",dp[0][0]);
    }
    return 0;
}

ProID  1080 Human Gene Function

这个题目跟那个最长公共子序列问题很像,搞生物信息学的同学对这个应该很熟悉了,这个题目应该算是最基础的,跟其他算法比起来可真是小巫见大巫了。
好了不说了。题目意思是,给两个字符串,包含ATGC,然后可以插入一个空格,根据题目中所给的二维表中的评分来确定最大分值(就是确定插入空格的位置)
PS: 注意了,两个字符串都可以插入空格,但是最终的字符串都必须长度相等。
状态转移方程为:
d[i][j]=max(d[i- 1][j-1]+match[ s1[i] ][ s2[j] ] , d[i-1][j]+match[ s1[i] ][ '-' ] , d[i][j-1]+match[ '-' ][ s2[j] ] );
这里的边界条件需要注意,跟那个LCS不太一样:
d[0][0]=0; d[i][0]=d[i-1][0] + match[ s1[i] ][ '-' ] ; d[0][i]=d[0][i-1] + match[ '-' ][ s2[i] ];
如果有d[i][j] = d[i-1][j]+match[ s1[i] ][ '-' ] 表示 s1[i-1] 与 s2[j] 匹配 ,
于是 s1[i] 就只能和 ‘-’(空格)匹配了。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <string>
using namespace std;


map<string,int>data;

int dp[110][110],match[110][110];
char s1[110],s2[110];

int T;

void init()
{
    match['A']['A']=match['C']['C']=match['G']['G']=match['T']['T']=5;
    match['A']['C']=match['C']['A']=match['A']['T']=match['T']['A']=-1;
    match[' ']['T']=match['T'][' ']=-1;
    match['A']['G']=match['G']['A']=match['C']['T']=match['T']['C']=-2;
    match['G']['T']=match['T']['G']=match['G'][' ']=match[' ']['G']=-2;
    match['A'][' ']=match[' ']['A']=match['C']['G']=match['G']['C']=-3;
    match['C'][' ']=match[' ']['C']=-4;
}

int main()
{
    
    init();
    while(scanf("%d",&T)!=EOF)
    {
        int n1,n2;
        while(T--)
        {
            scanf("%d %s",&n1,s1+1);
            scanf("%d %s",&n2,s2+1);
            memset(dp,0,sizeof(dp));
	    //初始化时需要特别注意
            for(int i=1;i<=n1;i++)
            {
                dp[i][0]=dp[i-1][0]+match[s1[i]][' '];
            }
            
            for(int i=1;i<=n2;i++)
            {
                dp[0][i]=dp[0][i-1]+match[' '][s2[i]];
            }
            for(int i=1;i<=n1;i++)
                for(int j=1;j<=n2;j++)
                {
                    int tmp1=dp[i-1][j-1]+match[s1[i]][s2[j]];
                    int tmp2=dp[i-1][j]+match[s1[i]][' '];
                    int tmp3=dp[i][j-1]+match[' '][s2[j]];

                    int res=max(tmp1,max(tmp2,tmp3));
                    dp[i][j]=res;
                }
                printf("%d\n",dp[n1][n2]);
        }
    }
    return 0
        ;
}



好了,时间也不早了,先整理这个前10到题目吧,话说这10道题目也是有难度的,虽然是第二次做,但是还是出了不少问题,所以即使现在糊了,可能过几天又给忘了,总之一句话,需要勤加练习才是。

大家晚安!


你可能感兴趣的:(dp,动态规划,杭电)