杭电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];elsedp[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;elsebreak;}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;//只要在这个房子里留够士兵,那么就可以获得全部的brainsfor(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;elseheigh=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.");elseprintf("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]);elseprintf("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");elseprintf("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道题目也是有难度的,虽然是第二次做,但是还是出了不少问题,所以即使现在糊了,可能过几天又给忘了,总之一句话,需要勤加练习才是。

大家晚安!

杭电60道DP问题总结(一)相关推荐

  1. 杭电60道DP问题总结(二)

    接着上一篇文章. 上次一次写的有点长了,保存好半天不成功,这次短一点吧,5道题目. 题目链接地址:http://acm.hdu.edu.cn/problemclass.php?id=516 ProID ...

  2. 杭电60道DP问题总结(三)

    接着前两篇,还是5道一贴吧. 题目链接地址:http://acm.hdu.edu.cn/problemclass.php?id=516 ProID 1159  Common Subsequence 题 ...

  3. 杭电46道DP牛人总结

    原文地址:杭电46道DP牛人总结作者:飞泉鸣玉 杭电46道DP牛人总结 HDU 动态规划(46道题目)倾情奉献~ [只提供思路与状态转移方程] 收藏 Robberies http://acm.hdu. ...

  4. 杭电60题--part 1 HDU1003 Max Sum(DP 动态规划)

    最近想学DP,锻炼思维,记录一下自己踩到的坑,来写一波详细的结题报告,持续更新. 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1003 Problem ...

  5. 杭电2151 Worm dp

    Worm Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submi ...

  6. 杭电2391--Filthy Rich(DP)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2391 题目意思:在一个n*m的矩阵中,不同的位置有不同重量的黄金,从矩阵的左上角开始,到右下角,只能向 ...

  7. 【HDU100】杭电入门一百道 C++ 全 题 解

    " Ctrl AC!一起 AC!" 题目集合链接:HDU2000-2099http://acm.hdu.edu.cn/listproblem.php?vol=11 前言 1. 好像 ...

  8. 杭电1284钱币兑换问题—背包dp/母函数(java)

    Problem Description 在一个国家仅有1分,2分,3分硬币,将钱N兑换成硬币有很多种兑法.请你编程序计算出共有多少种兑法. Input 每行只有一个正整数N,N小于32768. Out ...

  9. 杭电1024 Max Sum Plus Plus状压dp(java)

    问题描述 现在我认为你已经在Ignatius.L的"最大总和"问题中得到了AC.为了成为一名勇敢的ACMer,我们总是向更难挑战的问题挑战自我.现在你面临着一个更困难的问题. 给定 ...

最新文章

  1. 同一份数据,Redis为什么要存两次?
  2. 洛谷精选 - 字符串合集
  3. 树形dp - BNU 39572 Usoperanto
  4. 内部办公网与IDC机房的GRE隧道配置实践
  5. 学习C++项目—— 搭建多进程网络服务框架,增加业务和日志,心跳机制
  6. 格式化代码 Intellij IDEA
  7. GAMES101 Rasteriztion
  8. 密码学40年|重要历史与人物
  9. 【java与智能设备】CH07_01_Android中的文件存储
  10. JavaSE学习脑图
  11. 采用生产者消费者模式爬取毛豆新车网
  12. 检查Telerik UI以使用UWP作为PVS-Studio的入门方法
  13. OpenGL ES (二)EGL介绍和使用
  14. KVM-Virsh指令详解
  15. vscode vetur 代码格式化优化设置(亲测有用)
  16. edt嵌入式确定性测试_CallSerially EDT和InvokeAndBlock(第1部分)
  17. 付宇泽四则运算试题生成,结对
  18. vue移动端用什么数据可视化插件_AntV F2+vue-cli构建移动端可视化视图
  19. 软件测试知识——MySQL常用命令
  20. 车载以太网100base-T1

热门文章

  1. 阿里巴巴架构大调整 最新人事组织架构出炉
  2. ipad怎么创建html文件夹,ipad怎么建立文件夹 ipad如何在桌面建立及删除文件夹
  3. ETL讲解,到底什么是ETL
  4. 微积分 重难点记录 三 无穷远处的极限与水平渐近线 + 画曲线的总结
  5. 模仿学习(Imitation Learning)概述
  6. linux mysql 备份恢复
  7. Kubernetes安装及使用踩坑填坑大全【详细步骤】
  8. Linux bind函数详解
  9. 关于 设置 无线打印机
  10. 终于有颜值在线的无线路由了,斐讯K2T让你的家居高级起来!