杭电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时在记录下此时的下表,这样就出结果了。

参考代码如下:

[cpp] view plain copy print ?
  1. #include<cstdio> 
  2. #include<cstring> 
  3. #include<cstdlib> 
  4. int a[100001]; 
  5. int dp[100001]; 
  6. int main() 
  7.     int T; 
  8.     scanf("%d",&T); 
  9.      
  10.         for(int i=0;i<T;i++) 
  11.         { 
  12.             int n; 
  13.             scanf("%d",&n); 
  14.             /*
  15.             设 f[x] 为以 a[x] 终止且包含 a[x] 的最大序列的和,有:f[1] = a[1];
  16.    f[x+1] = f[x] > 0 ? f[x] + a[x+1] : a[x+1]
  17. 那么最大子序列的和就是 f[1] .. f[n] 中最大的一个。
  18.             */ 
  19.             memset(dp,0,sizeof(dp)); 
  20.             for(int j=0;j<n;j++) 
  21.                 scanf("%d",&a[j]); 
  22.             dp[0]=a[0]; 
  23.             for(int k=1;k<n;k++) 
  24.             { 
  25.                 if(dp[k-1]>0) 
  26.                     dp[k]=dp[k-1]+a[k]; 
  27.                 else 
  28.                     dp[k]=a[k]; 
  29.             } 
  30.             int start=0; 
  31.             int max=dp[0]; 
  32.             for(int k=1;k<n;k++) 
  33.             { 
  34.                 if(max<dp[k]) 
  35.                 { 
  36.                     start=k; 
  37.                     max=dp[k]; 
  38.                 } 
  39.             } 
  40.             int end=start; 
  41.             for(int k=end-1;k>=0;k--) 
  42.             { 
  43.                 if(dp[k]>=0) 
  44.                     end=k; 
  45.                 else 
  46.                     break
  47.             } 
  48.              
  49.              
  50.             printf("Case %d:\n",i+1); 
  51.             printf("%d %d %d\n",max,end+1,start+1); 
  52.             if(i!=T-1) 
  53.                 printf("\n"); 
  54.         } 
  55.  
  56.  
  57.     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来分层进行动态规划。 这里的状态转移方程是根据与之相连的点所决定的,因为只能从当前节点走到与之相连的点。注释在代码中写的很清楚了。

代码如下:

[cpp] view plain copy print ?
  1. #include<iostream> 
  2. #include<cstdio> 
  3. #include<cstring> 
  4. #include<algorithm> 
  5. using namespace std; 
  6. struct Room 
  7.     int bugs; 
  8.     int brains; 
  9. }; 
  10. Room rooms[101]; 
  11. int value[101][101]; 
  12. int matrix[101][101]; 
  13. int visited[101]; 
  14. int n,m; 
  15. void dpTree(int u) 
  16.     visited[u]=1; 
  17.     int r=(rooms[u].bugs+19)/20; //这个r表示当前这个房间里需要留几个士兵,即使是1个虫子也需要一个兵。 
  18.     for(int i=m;i>=r;i--) 
  19.         value[u][i]=rooms[u].brains;//只要在这个房子里留够士兵,那么就可以获得全部的brains 
  20.     for(int v=1;v<=n;v++) 
  21.     { 
  22.         if(matrix[u][v] && !visited[v])//选择当前点相邻的点,并且此边没有访问过,注意只能往下走而不能走回去 
  23.         { 
  24.             dpTree(v); 
  25.             for(int j=m;j>=r;j--) 
  26.                 for(int k=1;k<=j-r;k++) 
  27.                     value[u][j]=max(value[u][j],value[u][j-k]+value[v][k]); 
  28.         }//这个状态转移方程的意思是,第u个房间里留j个士兵所获得brains是由在第u个房间里留j-k个士兵,在与之相连的第v个房间里就k个士兵所获得的brains所决定的。一定记住了,当前的状态仅仅由与其相连的点的状态所决定!!! 
  29.     } 
  30. int main() 
  31.     while(scanf("%d %d",&n,&m)!=EOF) 
  32.     { 
  33.         if(n==-1) 
  34.             break
  35.         memset(rooms,0,sizeof(rooms)); 
  36.         memset(matrix,0,sizeof(matrix)); 
  37.         memset(visited,0,sizeof(visited)); 
  38.         memset(value,0,sizeof(value)); 
  39.         for(int i=1;i<=n;i++) 
  40.             scanf("%d %d",&rooms[i].bugs,&rooms[i].brains); 
  41.         for(int i=1;i<n;i++) 
  42.         { 
  43.             int u,v; 
  44.             scanf("%d %d",&u,&v); 
  45.             matrix[u][v]=matrix[v][u]=1; 
  46.         } 
  47.         if(m==0) 
  48.         { 
  49.             printf("0\n"); 
  50.             continue
  51.         } 
  52.         dpTree(1); 
  53.         printf("%d\n",value[1][m]); 
  54.     } 
  55.     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个一维数组记录当前和原始状态就可以了。具体解释在代码中。

代码如下:

[cpp] view plain copy print ?
  1. #include<cstdio> 
  2. #include<cstring> 
  3. #include<algorithm> 
  4.  
  5. using namespace std; 
  6. int curr_best[1000010];//保存当前状态 
  7. int prev_best[1000010]; 
  8. int max_sum,i,j; 
  9. int n,m; 
  10. int data[1000010]; 
  11. #define MIN_SUM 0x80000000 
  12. int main() 
  13.     while(scanf("%d %d",&m,&n)!=EOF) 
  14.     { 
  15.         for(int i=1;i<=n;i++) 
  16.             scanf("%d",&data[i]); 
  17.         memset(curr_best,0,sizeof(curr_best)); 
  18.         memset(prev_best,0,sizeof(prev_best)); 
  19.         int j=0; 
  20.         for(int i=1;i<=m;++i) 
  21.         { 
  22.             max_sum=MIN_SUM; 
  23.             for( j=i;j<=n;++j) 
  24.             { 
  25.                 //curr  b(i,*); 
  26.                 //prev b(i-1,*); 
  27.         curr_best[j]=max(curr_best[j-1],prev_best[j-1])+data[j]; 
  28.                 prev_best[j-1]=max_sum;//这两条语句不能写反了,这块我纠结了好久,解释一下,prev_best[j-1]表示的是上一个状态中i...j-1的最大值,max_sum更新之后表示的i...j的最大值,所以不能写反了 
  29.         max_sum=max(max_sum,curr_best[j]); 
  30.             } 
  31.             prev_best[j-1]=max_sum;//prev_best[j-1]中始终保持着前一个状态的最大值,这个很重要 
  32.         } 
  33.         printf("%d\n",max_sum); 
  34.     } 
  35.     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:注意了,这种算法只能得到最终的数据个数,但是如果需要得到具体的内容就不行了。

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

代码如下:

[cpp] view plain copy print ?
  1. #include <iostream> 
  2. #include <cstdio> 
  3. #include <cstring> 
  4. #include <algorithm> 
  5. using namespace std; 
  6. int  num[50001]; 
  7. int  ans[50001]; 
  8. int main() 
  9.     int n,k=1; 
  10.     int len,low,heigh,mid; 
  11.     int tmp1,tmp2; 
  12.     while(scanf("%d",&n)!=EOF) 
  13.     { 
  14.         for(int i=0;i<n;i++) 
  15.         { 
  16.             scanf("%d %d",&tmp1,&tmp2); 
  17.             num[tmp1]=tmp2; 
  18.         } 
  19.         memset(ans,0,sizeof(ans)); 
  20.         ans[1]=num[1]; 
  21.         len=1; 
  22.         for(int i=2;i<=n;i++) 
  23.         { 
  24.             low=1; 
  25.             heigh=len; 
  26.             while(low<=heigh) 
  27.             { 
  28.                 mid=(low+heigh)/2; 
  29.                 if(ans[mid]<num[i]) 
  30.                     low=mid+1; 
  31.                 else 
  32.                     heigh=mid-1; 
  33.             } 
  34.             ans[low]=num[i]; 
  35.             if(low>len) 
  36.                 len++; 
  37.         } 
  38.         printf("Case %d:\n",k); 
  39.         if(len==1) 
  40.             printf("My king, at most 1 road can be built."); 
  41.         else 
  42.             printf("My king, at most %d roads can be built.",len);         
  43.  
  44.         printf("\n\n"); 
  45.         k++; 
  46.     } 
  47.     return 0; 

ProID  1058 Humble Number

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

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

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

代码如下:

[cpp] view plain copy print ?
  1. #include <iostream> 
  2. #include <cstdio> 
  3. #include <cstring> 
  4. #include <algorithm> 
  5. using namespace std; 
  6.  
  7. int n; 
  8. typedef long long LL; 
  9. LL data[5843]; 
  10.  
  11. void init() 
  12.  
  13.     int i=1,j=1,m=1,n=1; 
  14.     int index=1; 
  15.     data[index]=1; 
  16.     while(1) 
  17.     { 
  18.         if(index==5843) 
  19.             break
  20.         LL tmp1=min(data[i]*2,data[j]*3); 
  21.         LL tmp2=min(data[m]*5,data[n]*7); 
  22.         LL res=min(tmp1,tmp2); 
  23.         if(res== data[i]*2) i++; 
  24.         else if(res== data[j]*3) j++; 
  25.         else if(res== data[m]*5) m++; 
  26.         else if(res== data[n]*7) n++; 
  27.          
  28.         if(res!= data[index]) 
  29.             data[++index]=res; 
  30.     } 
  31. int main() 
  32.     init(); 
  33.     while(scanf("%d",&n)!=EOF,n) 
  34.     { 
  35.         if(n%10==1 && n%100!=11)  
  36.             printf("The %dst humble number is %lld.\n",n,data[n]); 
  37.         else if(n%10==2 && n%100!=12) 
  38.             printf("The %dnd humble number is %lld.\n",n,data[n]); 
  39.         else if(n%10==3 && n%100!=13) 
  40.             printf("The %drd humble number is %lld.\n",n,data[n]); 
  41.         else 
  42.             printf("The %dth humble number is %lld.\n",n,data[n]); 
  43.     } 
  44.     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背包,而是多重背包问题,只要以总价值的一半为背包重量,那么只要能装满,就可以平均分。

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

[cpp] view plain copy print ?
  1. #include <iostream> 
  2. #include <cstdio> 
  3. #include <cstring> 
  4. #include <algorithm> 
  5. using namespace std; 
  6.  
  7. int data[7]; 
  8. int dp[500001]; 
  9. int m; 
  10.  
  11. void ZeroOnePack(int value,int weight) 
  12.     for(int i=m;i>=value;i--) 
  13.         dp[i]=max(dp[i-value]+weight,dp[i]); 
  14. void CompletePack(int value,int weight) 
  15.     for(int i=value;i<=m;i++) 
  16.         dp[i]=max(dp[i],dp[i-value]+weight); 
  17. void MultiPack(int value,int weight,int amount) 
  18.     if(value*amount>=m) 
  19.     { 
  20.         CompletePack(value,weight); 
  21.         return
  22.     } 
  23.     for(int k=1;k<amount;k*=2) 
  24.     { 
  25.         ZeroOnePack(k*value,k*weight); 
  26.         amount-=k; 
  27.     } 
  28.     ZeroOnePack(amount*value,amount*weight); 
  29. int main() 
  30.     int count=1; 
  31.     while(scanf("%d",&data[1])!=EOF) 
  32.     { 
  33.  
  34.         for(int i=2;i<=6;i++) 
  35.             scanf("%d",&data[i]); 
  36.         bool out=true
  37.         int sum=0; 
  38.         for(int i=1;i<=6;i++) 
  39.         { 
  40.             sum+=data[i]*i; 
  41.             if(data[i]) 
  42.                 out=false
  43.         } 
  44.         if(out) 
  45.             break
  46.         printf("Collection #%d:\n",count++); 
  47.         if((sum&1) ==1) 
  48.         { 
  49.             printf("Can't be divided.\n"); 
  50.             printf("\n"); 
  51.             continue
  52.         } 
  53.         m=sum/2; 
  54.         int value=0; 
  55.         memset(dp,0,sizeof(dp)); 
  56.         for(int i=1;i<=6;i++) 
  57.             if(data[i]) 
  58.                 MultiPack(i,i,data[i]); 
  59.             if(dp[m]==m) 
  60.                 printf("Can be divided.\n"); 
  61.             else 
  62.                 printf("Can't be divided.\n"); 
  63.             printf("\n"); 
  64.     } 
  65.     return 0; 

ProID  1069 Monkey and Banana

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

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

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

不多解释了,代码如下:

[cpp] view plain copy print ?
  1. #include<iostream> 
  2. #include<cstdio> 
  3. #include<cstring> 
  4. #include<algorithm> 
  5. using namespace std; 
  6. struct _data 
  7.     int x,y,z,area; 
  8. }; 
  9. _data data[200]; 
  10.  
  11. bool com(_data d1,_data d2) 
  12.     return d1.area<d2.area; 
  13. int n; 
  14. int dp[200]; 
  15. int main() 
  16.     int count=1; 
  17.     while(scanf("%d",&n)!=EOF&&n) 
  18.     { 
  19.         int index=0; 
  20.         for(int i=0;i<n;i++) 
  21.         { 
  22.             int x,y,z; 
  23.             scanf("%d %d %d",&x,&y,&z); 
  24.             data[index].x=x;data[index].y=y;data[index].z=z;data[index++].area=x*y; 
  25.             data[index].x=y;data[index].y=x;data[index].z=z;data[index++].area=x*y; 
  26.             data[index].x=y;data[index].y=z;data[index].z=x;data[index++].area=z*y; 
  27.             data[index].x=z;data[index].y=y;data[index].z=x;data[index++].area=z*y; 
  28.             data[index].x=x;data[index].y=z;data[index].z=y;data[index++].area=x*z; 
  29.             data[index].x=z;data[index].y=x;data[index].z=y;data[index++].area=x*z; 
  30.         } 
  31.         sort(data,data+index,com); 
  32.         memset(dp,0,sizeof(dp)); 
  33.         dp[0]=data[0].z; 
  34.         for(int i=1;i<index;i++) 
  35.         { 
  36.             int temp=0; 
  37.             for(int j=0;j<i;j++) 
  38.                 if(data[i].x>data[j].x && data[i].y>data[j].y&&temp<dp[j]) 
  39.                     temp=dp[j]; 
  40.                 dp[i]=temp+data[i].z; 
  41.         } 
  42.         int ans=dp[0]; 
  43.             for(int i=1;i<index;i++) 
  44.                 ans=max(ans,dp[i]); 
  45.         printf("Case %d: maximum height = %d\n",count++,ans);  
  46.     } 
  47.     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转换过来的。

代码如下:

[cpp] view plain copy print ?
  1. #include<iostream> 
  2. #include<string> 
  3. #include<cstring> 
  4. #include<cstdio> 
  5. using namespace std; 
  6.  
  7. struct Work 
  8.     char name[105]; 
  9.     int deadline; 
  10.     int cost; 
  11. }work[20]; 
  12.  
  13. struct DP 
  14.     int pre; 
  15.     int res; 
  16.     int cost; 
  17. }dp[40005]; 
  18. int N; 
  19.  
  20. void out(int state) 
  21.     if(state>0) 
  22.     { 
  23.         out(dp[state].pre); 
  24.         int t=dp[state].pre; 
  25.         int ans=t ^ state;//找出当前状态和上一状态的不同之处 
  26.         //也就是当前的课程 
  27.         int idx=0; 
  28.         //找出该十进制数在二进制表示下,1出现在什么位置,因为1代表完成了哪个课程。 
  29.         while(ans>0) 
  30.         { 
  31.             idx++; 
  32.             ans>>=1; 
  33.         } 
  34.         printf("%s\n",work[idx].name); 
  35.     } 
  36.  
  37. int main() 
  38.     int T; 
  39.     scanf("%d",&T); 
  40.     while(T--) 
  41.     { 
  42.         scanf("%d",&N); 
  43.         for(int i=1;i<=N;i++) 
  44.             scanf("%s %d %d",&work[i].name,&work[i].deadline,&work[i].cost); 
  45.         int Max=(1<<N )-1; 
  46.         dp[0].cost=0; 
  47.         dp[0].pre=-1; 
  48.         dp[0].res=0; 
  49.  
  50.         for(int i=1;i<=Max;i++) 
  51.         { 
  52.             dp[i].cost=0; 
  53.             dp[i].pre=-1; 
  54.             dp[i].res=0xfffffff; 
  55.             for(int j=0;j<N;j++) 
  56.             { 
  57.                 int cur=1<<j; 
  58.                 if(i & cur) //包含了该课程 
  59.                 {//状态,pre是上一个状态,res是需要扣的分,cost是达到当前状态需要花费的时间 
  60.                     dp[i].cost=dp[i^cur].cost+work[j+1].cost; 
  61.                     int reduce=dp[i].cost-work[j+1].deadline; 
  62.                     if(reduce<0) 
  63.                         reduce=0; 
  64.                     reduce += dp[i^cur].res; 
  65.                     //因为题目中的课程输入已经保证是字典序,所以==的时候一样更新 
  66.                     if(dp[i].res>=reduce) 
  67.                     { 
  68.                         dp[i].res=reduce; 
  69.                         dp[i].pre=i^cur; 
  70.                     } 
  71.                 } 
  72.             } 
  73.         } 
  74.         printf("%d\n",dp[Max].res); 
  75.         out(Max); 
  76.     } 
  77.     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]。


代码如下:

[cpp] view plain copy print ?
  1. #include <iostream> 
  2. #include <cstdio> 
  3. #include <cstring> 
  4. #include <algorithm> 
  5. using namespace std; 
  6.  
  7. int data[101][101]; 
  8. int dp[101][101];//dp[i][j]表示从位置(i,j)为起点时能吃到最多的 
  9.  
  10. int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 
  11.  
  12. int n,k; 
  13.  
  14. int Dfs(int x,int y) 
  15.     if(dp[x][y]>0)//如果已经有值了,可以直接返回 
  16.         return dp[x][y]; 
  17.     int Max=0; 
  18.     for(int i=0;i<4;i++)//搜四个方向 
  19.         for(int j=1;j<=k;j++)//一次可以跳1至k步 
  20.         { 
  21.             int tx=x+dir[i][0]*j; 
  22.             int ty=y+dir[i][1]*j; 
  23.         //必须跳的位置的奶酪比当前所站的位置多 
  24.             if(tx>=0 && tx<n && ty>=0 && ty<n && data[x][y]<data[tx][ty]) 
  25.             { 
  26.                 int temp=Dfs(tx,ty); 
  27.                 Max=max(Max,temp); 
  28.             } 
  29.         } 
  30.          
  31.     dp[x][y]=Max+data[x][y]; 
  32.     return dp[x][y]; 
  33.  
  34. int main() 
  35.     while(scanf("%d %d",&n,&k)!=EOF) 
  36.     { 
  37.         if(n==-1 && k==-1) 
  38.             break
  39.         for(int i=0;i<n;i++) 
  40.             for(int j=0;j<n;j++) 
  41.                 scanf("%d",&data[i][j]); 
  42.         memset(dp,0,sizeof(dp)); 
  43.         Dfs(0,0); 
  44.         printf("%d\n",dp[0][0]); 
  45.     } 
  46.     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] 就只能和 ‘-’(空格)匹配了。

代码如下:

[cpp] view plain copy print ?
  1. #include <iostream> 
  2. #include <cstdio> 
  3. #include <cstring> 
  4. #include <algorithm> 
  5. #include <map> 
  6. #include <string> 
  7. using namespace std; 
  8.  
  9.  
  10. map<string,int>data; 
  11.  
  12. int dp[110][110],match[110][110]; 
  13. char s1[110],s2[110]; 
  14.  
  15. int T; 
  16.  
  17. void init() 
  18.     match['A']['A']=match['C']['C']=match['G']['G']=match['T']['T']=5; 
  19.     match['A']['C']=match['C']['A']=match['A']['T']=match['T']['A']=-1; 
  20.     match[' ']['T']=match['T'][' ']=-1; 
  21.     match['A']['G']=match['G']['A']=match['C']['T']=match['T']['C']=-2; 
  22.     match['G']['T']=match['T']['G']=match['G'][' ']=match[' ']['G']=-2; 
  23.     match['A'][' ']=match[' ']['A']=match['C']['G']=match['G']['C']=-3; 
  24.     match['C'][' ']=match[' ']['C']=-4; 
  25.  
  26. int main() 
  27.      
  28.     init(); 
  29.     while(scanf("%d",&T)!=EOF) 
  30.     { 
  31.         int n1,n2; 
  32.         while(T--) 
  33.         { 
  34.             scanf("%d %s",&n1,s1+1); 
  35.             scanf("%d %s",&n2,s2+1); 
  36.             memset(dp,0,sizeof(dp)); 
  37.         //初始化时需要特别注意 
  38.             for(int i=1;i<=n1;i++) 
  39.             { 
  40.                 dp[i][0]=dp[i-1][0]+match[s1[i]][' ']; 
  41.             } 
  42.              
  43.             for(int i=1;i<=n2;i++) 
  44.             { 
  45.                 dp[0][i]=dp[0][i-1]+match[' '][s2[i]]; 
  46.             } 
  47.             for(int i=1;i<=n1;i++) 
  48.                 for(int j=1;j<=n2;j++) 
  49.                 { 
  50.                     int tmp1=dp[i-1][j-1]+match[s1[i]][s2[j]]; 
  51.                     int tmp2=dp[i-1][j]+match[s1[i]][' ']; 
  52.                     int tmp3=dp[i][j-1]+match[' '][s2[j]]; 
  53.  
  54.                     int res=max(tmp1,max(tmp2,tmp3)); 
  55.                     dp[i][j]=res; 
  56.                 } 
  57.                 printf("%d\n",dp[n1][n2]); 
  58.         } 
  59.     } 
  60.     return 0   ; 

博客转自:http://blog.csdn.net/xueerfei008/article/details/9143783

你可能感兴趣的:(杭电60道DP问题总结一)