动态规划题目

动态规划算法,在T大某位老师的书中说就是递推+重复子问题。

动态规划算法的效率主要与重复子问题的处理有关。

典型的题目有 陪审团,最大公共子串问题

1,最大公共子串问题

这个是动态规划的基础题目。动态规划就是递推和重复子结构。

确定了递推关系后。找到一个能极大地减少重复运算的子结构至关重要。选的好了,时间效率会很好。

这个问题,不妨设第一个串为a,长度为n,第二个串为b,长度m。那么最长的子序列长度为f(n,m)

当a[n]=a[m]时

f(n,m)=1+f(n-1,m-1)

否则f(n,m)=max(f(n-1),f(m-1))

同时建立一个存储计算过的f(x,y)的矩阵,如果计算过了就直接使用

程序如下所示

[cpp] view plaincopy

  1. #include <iostream>

  2. #define MAXLen 1000

  3. char seq1[MAXLen];

  4. char seq2[MAXLen];

  5. int maxLen[MAXLen][MAXLen];

  6. int MaxCommonMain()

  7. {

  8. while(scanf("%s%s",seq1+1,seq2+1)>0)

  9. {
  10. int length1=strlen(seq1+1);

  11. int length2=strlen(seq2+1);
  12. for(int i=0;i<=length1;i++)

  13. maxLen[i][0]=0;

  14. for(int j=0;j<=length2;j++)

  15. maxLen[0][j]=0;
  16. for(int i=1;i<=length1;i++)

  17. {

  18. for(int j=1;j<=length2;j++)

  19. {

  20. if(seq1[i]==seq2[j])

  21. maxLen[i][j]=maxLen[i-1][j-1]+1;

  22. else

  23. maxLen[i][j]=maxLen[i-1][j]>maxLen[i][j-1]?maxLen[i-1][j]:maxLen[i][j-1];

  24. }

  25. }
  26. printf("%d/n",maxLen[length1][length2]);
  27. // printf("%d",maxLen[length2-1][length1-1]);
  28. }

  29. return 0;

  30. }

2,陪审团问题

问题描述:

问题描述
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中
挑选的。先随机挑选n
个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。
选m 人的办法是:
控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0
到20。为了公
平起见,法官选出陪审团的原则是:选出的m
个人,必须满足辩方总分和控方总分的差的
绝对值最小。如果有多种选择方案的辩方总分和控方总分的之差的绝对值相同,那么选辩控
双方总分之和最大的方案即可。最终选出的方案称为陪审团方案。
输入数据
输入包含多组数据。每组数据的第一行是两个整数n
和m,n 是候选人数目,m 是陪审
团人数。注意,1<=n<=200, 1<=m<=20 而且 m<=n。接下来的n
行,每行表示一个候选人
的信息,它包含2
个整数,先后是控方和辩方对该候选人的打分。候选人按出现的先后从1
开始编号。两组有效数据之间以空行分隔。最后一组数据n=m=0
输出要求
对每组数据,先输出一行,表示答案所属的组号,
如 ‘Jury #1‘, ‘Jury #2‘,
等。接下来的
一行要象例子那样输出陪审团的控方总分和辩方总分。再下来一行要以升序输出陪审团里每
个成员的编号,两个成员编号之间用空格分隔。每组输出数据须以一个空行结束。
输入样例
4
2
1 2
2 3
4 1
6 2
0 0
输出样例
Jury #1
Best jury has value 6
for prosecution and value 4 for defence:
2 3

用动态规划来解

动态规划可以看成是重复子问题+递归

设p为控方向量,d为辩方向量

评分从-400到400
f(j,k)为选取第j个人,评分差为k的辩控总分
对于任意的i,i大于0小于n,则有
f(j,k)=f(j-1+1,x+p[i]-d[i])
则就有
f(j,k)=f(j-1,x)+p[i]+d[i];

如果新的f(j,k)大于旧的,则替换旧的,否则不替换

初始值为k=0,在程序中或许是某个值,以免避免序号k出现负值。
那么初始的f(0,0)=0;
在第一轮的时候只有辩控差为0的合理
对所有的候选人做
f(1,p[i]-d[i])=max(f(1,p[i]-d[i]),f(0,0)+p[i]+d[i]);
这样就可以完成选一个人的操作。
做完之后离k=0最近的非负的就是我们要的最大的辩控和。下标就是辩控差。
同理,如果选两个的话,第二轮
k=-400~400
如果f(1,k)合理(此时,只有在第一轮中算出来的合理)
f(2,k+p[i]-d[i])=max(f(2,k+p[i]-d[i]),f(1,k)+p[i]+d[i]);

如果要保存搜索得到的路径
设路径path保存路径向量
path(j,k)保存推荐第j个陪审员时候,辩控差为k的候选人。
对应上面的f函数,初始值都设为0。
如果只找一个候选人
则本身path中没有任何的值,即没有推荐任何的人。也就是说,推荐第一个人的时候,谁都可以(若推荐第二个人,则以前推荐过的不能再次推荐)
在推荐第一个候选人的时候,只管保存path[0][p[i]-d[i]]=i
;

当选取多个候选人的时候,在更新f(j,k)的同时更新path,因为更新f(j,k)的同时,意味着选了新的候选人,所以需要更新路径

当以上都做完之后,
定义一个数组result来取路径
对i:1~m
 result[i]=path[m-i+1][k];
 k=k-p[result[i]]+d[result[i]];
这样就都解决了。

[cpp] view plaincopy

  1. #include<stdlib.h>

  2. #include<iostream>

  3. #include<memory.h>

  4. int p[300];

  5. int d[300];

  6. int path[30][1000];

  7. int f[30][1000];

  8. int result[30];

  9. int compareInt(const void *el, const void *e2)

  10. {

  11. return *((int *)el)-*((int *)e2);

  12. }

  13. int jurymain()

  14. {

  15. int n,m;

  16. scanf("%d%d",&n,&m);

  17. int no=0;

  18. int i=0;

  19. while(n+m)//n=0,m=0结束条件

  20. {

  21. no++;

  22. for(i=1;i<=n;i++)

  23. scanf("%d%d",&p[i],&d[i]);

  24. memset(f,-1,sizeof(f));

  25. memset(path,0,sizeof(path));

  26. int minD=20*m;

  27. f[0][minD]=0;

  28. for(int j=0;j<m;j++)

  29. {

  30. for(int k=0;k<=minD*2;k++)

  31. {

  32. if(f[j][k]>=0)

  33. {

  34. i=1;

  35. int temp1;

  36. int temp2;

  37. for(;i<=n;i++)

  38. {

  39. if(f[j][k]+p[i]+d[i]>f[j+1][k+p[i]-d[i]])

  40. {

  41. temp1=j;

  42. temp2=k;

  43. while(temp1>0&&path[temp1][temp2]!=i)

  44. {
  45. temp2=temp2-p[path[temp1][temp2]]+d[path[temp1][temp2]];

  46. temp1--;
  47. }

  48. if(temp1==0)

  49. {

  50. f[j+1][k+p[i]-d[i]]=f[j][k]+p[i]+d[i];

  51. path[j+1][k+p[i]-d[i]]=i;

  52. }

  53. }
  54. }

  55. }

  56. }

  57. }

  58. i=minD;

  59. int j=0;

  60. int k=0;

  61. while(f[m][i+j]<0&&f[m][i-j]<0)

  62. j++;

  63. if(f[m][i+j]>f[m][i-j])

  64. k=i+j;

  65. else

  66. k=i-j;
  67. printf("Jury #%d/n",no);

  68. printf("Best jury has value %d for prosecution and value %d for defence:/n",(k-minD+f[m][k])/2,(minD-k+f[m][k])/2);
  69. for(i=1;i<=m;i++)

  70. {

  71. result[i]=path[m-i+1][k];

  72. k-=p[result[i]]-d[result[i]];

  73. //  std::cout<<"result "<<i<<" "<<result[i]<<std::endl;

  74. }

  75. qsort(result+1,m,sizeof(int),compareInt);

  76. for(i=1;i<=m;i++)

  77. printf("%d ",result[i]);

  78. printf("/n/n");

  79. scanf("%d%d",&n,&m);
  80. }

  81. //system("pause");

  82. return 0;

  83. }

3,小花店问题

F束花从左到右放在V个花瓶里面(1<=F<=V<=100),花要按照花的标志数从小到大排列。不同的花在不同的花瓶能够产生不同的美学价值。v[i,j]来表示第i朵花在第j个花瓶产生的美学价值。求把n朵花放在m个花瓶能够产生的最大的美学价值。

这个题和最大公共子串的思考角度相似。由于花必须要小于等于瓶子。而且花的编号由小到大,不能乱序。例如就不能把出现【2,4】
【1,5】这种现象。也就是说插在花瓶中的花按照花瓶的顺序,序号升序排列。

不妨用f(i,j)来表示把前i朵花插入前个瓶子中。当i=j时,f(i,j)=v[1,1]+v[2,2]+...+v[i,i];

当i=0时,f(0,j)=0; 当i!=j, 且i!=0时f(i,j)=max(f(i,j-1),f(i-1,j-1)+v[i,j])
,i<=j-1.

[cpp] view plaincopy

  1. #include <stdlib.h>

  2. #include <stdio.h>

  3. void getMaxValue(int **f,int **v,int m,int n)

  4. {
  5. for(int j=1;j<=m;j++)

  6. {

  7. for(int i=1;i<=j-1;i++)

  8. {

  9. if(f[i-1][j-1]+v[i][j]>f[i][j-1])

  10. f[i][j]=f[i-1][j-1]+v[i][j];

  11. else

  12. f[i][j]=f[i][j-1];

  13. }

  14. }
  15. }

  16. int main()

  17. {

  18. int n,m;

  19. scanf("%d%d",&n,&m);

  20. int **v=new int[n+1][m+1];

  21. int **f=new int[n+1][m+1];

  22. v[0][0]=0;

  23. for(int i=1;i<=n;i++)

  24. for(int j=1;j<=m;j++)

  25. scanf("$d",v[i][j]);
  26. for(int j=0;j<=m;j++)

  27. v[0][j]=0;
  28. int tempSum=0;

  29. for(int i=0;i<=n;i++)

  30. {

  31. sum+=v[i][i];

  32. f[i][i]=sum;

  33. }

  34. getMaxValue(f,v,m,n);
  35. delete(v);

  36. return 0;
  37. }

4,最佳旅行线问题

简单描述:某航空公司给的奖品,一张免费的机票。可以从最西边的一个城市出发,必须到达最东边的一个城市,然后返回起点城市。每个城市职能经过一次,起点被访问2次。问最多能够访问多少个城市。

这个题可以使用动态规划的方法。这个题目也使我想起了《算法导论》中装配线的问题,其实基本是一致的。

在很多的书或者是解法中,都用了这样的方法:从起点开始,两条路一起走f[i,j]表示两条不相交的路线分别到达i和j时,所需乘坐航线的最大值

f(1,1)=0;

f(i,j)= max{f(k,j)}+1,i>j ;k是i的前驱节点。

max{f(i,k)}+1,i<j

f(i,i)无意义

该算法是没有错的。很多地方也用这个算法做了一些程序版本。但是这个算法有一个限制,就是前驱节点。在t大某位老师的书中,他枚举所有的点对,然后对f矩阵进行更新(传统的动态规划方法)。但是他这个有一个前提,那就是所有节点的序号符合拓扑序列。但是题目中并没有说城市符合拓扑序列。假如说序号比较随意,t大的这本书中的就错了。但是他并没有在代码中说明,算是对读者的一个误导。

[cpp] view plaincopy

  1. #ifndef TICKETAWARD_H

  2. #define TICKETAWARD_H

  3. class TicketAward

  4. {

  5. int *b;//度数

  6. int **g;//g(i,j)表示第i个点的第j个孩子

  7. int **f;//函数f

  8. int n;//总共有n个节点

  9. public:

  10. TicketAward();

  11. int max(int x,int y);

  12. };

  13. #endif

[cpp] view plaincopy

  1. #include "stdafx.h"

  2. #include <iostream>

  3. #include "TicketAward.h"

  4. TicketAward::TicketAward()

  5. {

  6. std::cin>>n;

  7. g=new int *[n+1];

  8. b=new int[n+1];

  9. f=new int*[n+1];

  10. for(int i=1;i<=n;i++)

  11. {

  12. g[i]=new int[n+1];

  13. f[i]=new int[n+1];

  14. b[i]=0;

  15. for(int j=1;j<=n;j++)

  16. {
  17. }

  18. }

  19. int temp=0;

  20. for(int i=1;i<=n;i++)

  21. {

  22. std::cin>>temp;//输入i的第一个孩子,如果不为0,即就是存在孩子节点,则继续

  23. while(temp!=0)

  24. {

  25. b[i]++;

  26. g[i][b[i]]=temp;

  27. std::cin>>temp;

  28. }

  29. }

  30. f[1][1]=0;

  31. for(int i=1;i<=n;i++)

  32. {
  33. for(int j=1;j<=b[i];j++)

  34. {

  35. if(i==j==1)

  36. continue;

  37. if(i<j)

  38. for(int k=1;k<=b[i];k++)

  39. {

  40. f[i][j]=max(f[i][j],(f[g[i][k]][j]+1));

  41. }

  42. if(i>j)

  43. for(int k=1;k<=b[j];k++)

  44. {

  45. f[i][j]=max(f[i][j],(f[g[j][k]][j]+1));

  46. }

  47. }

  48. }

  49. std::cout<<(f[n][n]-2);

  50. }

  51. int TicketAward::max(int x, int y)

  52. {

  53. return x>y?x:y;

  54. }

5, 最长词链问题

给定一个仅仅包含小写字母的英文单词表,其中每个单词至少包含一个字母,最多包含75个字母,且按照字典序排列。所有单词包含的单词个数之和不超过200w

如果在一张由一个单词或者多个单词组成的表中,每个单词都被其后的一个单词所包含,则成此表为一个链。

现在要求从单词表中找到一个包含单词书最多的最长词链。

该问题可以由动态规划解。这让我想起了一个题:在一个连续的长字符串中,找出匹配次数最多的一个词组,不能有重叠。相同的方法。

令F(k)表示第k个单词的时候的最长的链。则F(k)=maxF(i)+1,i从1,到k-1,并且第i个单词是第k个的前缀。这儿问题就解了。

但是,对于这道题还有其他的解法。由于字典序并非乱序。每个单词的前一个单词所包含的前缀很可能就是这个单词的前缀,并且前一个单词也可能就是该单词的前缀。因为前缀的特殊性。所以只用检查前一个单词的前缀(包括该单词本身)就能找出该前缀链

代码为非动态规划算法

[cpp] view plaincopy

  1. #include "stdafx.h"

  2. #ifndef MOSTLENGTHLETTER_H

  3. #define MOSTLENGTHLETTER_H

  4. #include <iostream>

  5. #include <string>

  6. struct Pnode

  7. {

  8. int len;

  9. Pnode *next[26];

  10. };

  11. struct MostLengthLetters

  12. {

  13. public:

  14. MostLengthLetters()

  15. {

  16. for(int i=0;i<=25;i++)

  17. {

  18. root.next[i]=NULL;

  19. }

  20. ans=0;

  21. std::cout<<"please input the num of words"<<std::endl;

  22. std::cin>>wordsNum;
  23. this->getWords();
  24. }

  25. void getWords();

  26. void update(std::string temp);

  27. int wordsNum;

  28. Pnode root;

  29. int ans;

  30. };

  31. void MostLengthLetters::getWords()

  32. {

  33. std::string temp;

  34. for(int i=0;i<this->wordsNum;i++)

  35. {

  36. std::cin>>temp;

  37. this->update(temp);

  38. }

  39. }

  40. void MostLengthLetters::update(std::string temp)

  41. {

  42. int len=temp.length();

  43. Pnode p=root;

  44. int max=0;

  45. int i=1;

  46. int tempInt=0;

  47. while(i<len)

  48. {

  49. tempInt=temp[i]-‘a‘;

  50. if(p.next[i]==NULL)

  51. {

  52. Pnode *node=new Pnode;

  53. node->len=0;

  54. for(int j=0;j<=25;j++)

  55. {

  56. node->next[j]=NULL;

  57. }

  58. }

  59. if(max<p.len)

  60. max=p.len;

  61. i++;

  62. p=*p.next[tempInt];

  63. }

  64. p.len=max+1;

  65. this->ans=p.len;

  66. return;

  67. }

  68. #endif

6, 整数划分问题

给定一个自然数,分成k部分,A1,A2..的数的和,要求A1<=A2...求有多少种?

原理:整数n拆分成最多不超过m个数的和的拆分数,和n 拆分成最大不超过m的拆分数相等。

根据这个原理,原问题就转化成了求最大拆分为k的拆分个数与最大拆分为k-1的拆分个数的差

f(n,k)=f(n,k-1)+f(n-k,k)。

[cpp] view plaincopy

  1. #include "stdafx.h"

  2. #ifndef SPLITTOKNUM_H

  3. #define SPLITTOKNUM_H

  4. #include<iostream>

  5. #include <memory.h>

  6. #define MaxN 100

  7. class SplitToKNum

  8. {
  9. public:

  10. SplitToKNum()

  11. {

  12. std::cin>>n;

  13. std::cin>>k;

  14. memset(f,0,sizeof(f));

  15. for(int i=1;i<=k;i++)

  16. {

  17. f[MaxN][i]=1;

  18. }

  19. for(int j=1;j<=k;j++)

  20. for(int i=MaxN+1;i<=MaxN+n;i++)

  21. f[i][j]=f[i][j-1]+f[i-j][j];

  22. std::cout<<f[n+MaxN][k]-f[n+MaxN][k-1]<<std::endl;
  23. }

  24. int n;

  25. int k;

  26. int f[2*MaxN+1][MaxN];

  27. };

  28. #endif

同时附上ferrer图的几个原理:

a,整数n拆分成k个数的和的拆分数,和数n拆分最大数位k的拆分数相等

b,整数n拆分成最多不超过m个数的和的拆分数,和n拆分成最大不超过m的拆分数相等。

c,整数n拆分成互不相同的若干奇数的和的的拆分数,和n拆分成自共轭的Ferrers图像的拆分数相等.

7,青蛙的烦恼

池塘里面有n片荷花,正好形成一个凸多边形,按照顺时针方向将这n片和也顺次编号为1,2,3,4,5...,n。一只青蛙想要跳过这些荷叶,每个只能跳过一次,希望跳过的距离最短。

输入荷叶数n,每片荷叶的坐标xy,输出最短距离。

分析:凸多边形,首先不能出现交叉路,否则,由两边之和大于第三边可以知道,这样实际上是路程变长了。

其次,直接按照凸多边形也不正确,可以参考平行四边形,其中一个角为30度,请自行画图查明。

方法:每次每个顶点只能到与自己相邻的两个中的一个(到其他顶点边会出现交叉),剩下的n-1个顶点也遵从这一规则。则可以使用动态规划的方法。

从s到L

则如果从s出发,则f(s,L,0)=min{dis(s,s+1)+f(s+1,L-1,0),f(s+1,L-1,1)+dis(s+L-1,s)}

如果从s+L-1出发则f(s,L,1)=min{dis(s,s+L-1)+f(s+L-1,L-1,0),f(s+L-1,L-1,0)+dis(s+L-1,s)}

[cpp] view plaincopy

  1. #include <stdlib.h>

  2. #include <stdio.h>

  3. #include <iostream>

  4. #define MAXN 1000

  5. double x[MAXN];// x radix

  6. double y[MAXN];

  7. double dis[MAXN][MAXN];

  8. int n;// the num of

  9. double f[MAXN][2][2];

  10. double min(double a,double b)

  11. {

  12. return a<b?a:b;

  13. }

  14. int main()

  15. {

  16. std::cout<<"please input the num of the points"<<std::endl;

  17. std::cin>>n;

  18. for(int i=0;i<n;i++)

  19. {

  20. std::cin>>x[i]>>y[i];

  21. }

  22. for(int i=0;i<n-1;i++)

  23. {

  24. for(int j=i+1;j<n;j++)

  25. {

  26. dis[i][j]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);

  27. dis[j][i]=dis[i][j];

  28. }

  29. }

  30. int sign=0;

  31. double ans=1000000;

  32. for(int j=1;j<=n;j++)

  33. {

  34. sign=1-sign;

  35. for(int i=n-1;i>=0;i--)

  36. {

  37. if(j==1)

  38. {

  39. f[i][sign][0]=0;

  40. f[i][sign][1]=0;

  41. }

  42. else

  43. {

  44. f[i][sign][1]=min(f[(i+1)%n][1-sign][1]+dis[i][(i+1)%n],f[(i+1)%n][1-sign][0]+dis[i][(i+j-1)%n]);

  45. f[i][sign][0]=min(f[i][1-sign][1]+dis[(i+j-1)%n][i],f[i][1-sign][0]+dis[(i+j-1)%n][(i+j-2)%n]);

  46. }

  47. if(j==n)

  48. ans=min(ans,f[i][sign][1]);

  49. }

  50. }

  51. //std::cout<<f[1][sign][1]<<"/t"<<f[1][sign][0]<<std::endl;

  52. std::cout<<ans<<std::endl;
  53. return 0;

  54. }

8排列问题

在整数1,2,。。。,n中,有些排列满足下面的性质:该排列中除了最后一个数字以外,每个整数后面都跟有一个同它相差为1的整数。例如:N=4,排列1432满足上述性质。求,如果
任意指定p个位置的值,求最多有几个排列满足如上性质

这种排列满足一下两条性质

a,任何一个排列的后k位是若干连续整数的集合

b,如果一个排列的后k位(1<=k<=n)是软干连续整数组成的集合,则这个排列满足题目所述的性质。

动态规划的方法即可。设s为起始的数的大小,r为连续的多少数的个数

g(s,r)=g(s+1,j-1),若第1个位置为s,即倒数第j个位置为s

g(s,j-1),若第1个位置为s+r-1,即倒数的第j个位置为s+r-1

不确定则为上述两个之和

代码如下

[cpp] view plaincopy

  1. #include <stdlib.h>

  2. #include <iostream>

  3. #include <memory.h>

  4. class RankProgram

  5. {

  6. public:

  7. RankProgram()

  8. {

  9. std::cout<<"please input the testing data"<<std::endl;

  10. std::cin>>n>>p;

  11. s=new int[n+2];

  12. g=new int* [n+2];

  13. for(int i=0;i<=n+1;i++)

  14. {

  15. g[i]=new int[n+2];

  16. memset(g[i],0,sizeof(int)*(n+2));

  17. g[i][1]=1;

  18. }

  19. memset(s,0,sizeof(int)*(n+2));

  20. int tempData;

  21. int tempP;

  22. for(int i=1;i<=p;i++)

  23. {

  24. std::cin>>tempP>>tempData;

  25. s[n-tempP+1]=tempData;

  26. }

  27. for(int i=n;i>=1;i--)

  28. {

  29. for(int j=2;j<=n-i+1;j++)

  30. {

  31. if(s[j]==i)

  32. g[i][j]=g[i+1][j-1];

  33. else if(s[j]==(i+j-1))

  34. g[i][j]=g[i][j-1];

  35. else

  36. g[i][j]=g[i+1][j-1]+g[i][j-1];

  37. }

  38. }

  39. std::cout<<g[1][n]<<std::endl;

  40. }

  41. ~RankProgram()

  42. {

  43. delete [] s;

  44. for(int i=0;i<=n+1;i++)

  45. delete [] g[i];

  46. delete [] g;

  47. }

  48. private:

  49. int *s;

  50. int **g;

  51. int p;

  52. int n;

  53. };

  54. int main()

  55. {

  56. RankProgram rp;

  57. }

9,画室问题

实际上该题可以描述为,给定一个尺寸为N的矩阵,行和列宽度为2^N,将该矩阵分为4分,左上角不为零,其他三部分都包含零。递归定义这三部分,直到最小单位,即矩阵只有一个数,为0;

求如果让该矩阵移动一个尺寸(x,y),两个矩阵重复部分的0的个数。

思考:实际上就是判断移动前和移动后对应的位置是否都为0,如果都为0,则总0个数+1。

如果移动到了左上角的位置则不加,其他三部分因为是一样的,该部分递归定义的上层+1

在下面的程序中,k1,k2 表示的位置,在当前区域的哪一块

a,b表示三个区域,其中左上的直接被忽略了。

kk1,kk2,表示移动到的了哪一个区域

if(!((a+p[i-1]+k1)%2==0&&(b+q[i-1]+k2)%2==1))表示移动到的区域的哪一块,如果是左上直接忽略

[c-sharp] view plaincopy

  1. #ifndef MATRIXMOVE_H

  2. #define MATRIXMOVE_H

  3. #include <iostream>

  4. #include <memory.h>

  5. #define MAXN 101

  6. class MatrixMove

  7. {

  8. public:

  9. MatrixMove()

  10. {

  11. std::cin>>n;
  12. memset(f,0,sizeof(f));

  13. f[n+1][0][0]=1;

  14. f[n+1][0][1]=f[n+1][1][0]=f[n+1][1][1]=0;

  15. std::cin>>x>>y;

  16. for(int i=1;i<=n;i++)

  17. {

  18. p[n-i+1]=x%2;

  19. q[n-i+1]=y%2;

  20. x=x/2;

  21. y=y/2;

  22. }

  23. for(int i=n+1;i>=2;i--)

  24. {

  25. for(int k1=0;k1<=1;k1++)

  26. for(int k2=0;k2<=1;k2++)

  27. for(int a=0;a<=1;a++)

  28. for(int b=0;b<=1;b++)

  29. {

  30. if(!(a==0&&b==1))

  31. {

  32. int kk1=(a+p[i-1]+k1)/2;

  33. int kk2=(b+q[i-1]+k2)/2;

  34. if(!((a+p[i-1]+k1)%2==0&&(b+q[i-1]+k2)%2==1))

  35. {

  36. f[i-1][kk1][kk2]+=f[i][k1][k2];

  37. }

  38. }

  39. }

  40. }

  41. std::cout<<f[1][0][0]<<std::endl;

  42. }

  43. private:

  44. int n;

  45. int x;

  46. int y;

  47. int f[MAXN][2][2];

  48. int p[MAXN];

  49. int q[MAXN];

  50. };

  51. #endif

10, 中世纪剑士

n个人决斗,两两之间有强弱关系,强弱关系不传递,例如a>b,b>c,c>a。n个剑士围成一个圈,一次抽签,抽中的人和他右边的人决斗,输了的人出圈。现在问是否存在一种决斗方式让第k个人生出,计算可能胜出的的人数和方案。

这个题目让我想起了围成一个圈的猴子的题目,那个题目是约瑟夫问题。

和这个不一样。

这个题目:一个人要胜出,则要胜了所有右边的人,同时也要胜出左边的人。因为是围成一个圈,所以该人胜出的话,最终肯定是自己跟自己相遇。那么,这种情况下,把圈展开成一个链,将该链延长一倍,如果i和i+n可以相遇,则说明i可以胜出。i人向右决斗,i+n向左决斗

如果两个人可以相遇,用meet[i,j]来表示

meet[i,j]= true if meet[i,k] and meet[k,j] and (e[i,k] or e[j,k])=true

false

[cpp] view plaincopy

  1. #ifndef ACIENTSOLDIER_H

  2. #define ACIENTSOLDIER_H

  3. #include <iostream>

  4. #include <memory.h>

  5. class AcientSoldier

  6. {

  7. public:

  8. AcientSoldier()

  9. {

  10. std::cin>>n;

  11. a=new int*[n+1];

  12. meet=new bool*[2*n+1];

  13. for(int i=1;i<=n;i++)

  14. {

  15. a[i]=new int[n+1];

  16. meet[i]=new bool[2*n+1];

  17. meet[i+n]=new bool[2*n+1];

  18. memset(meet[i],0,sizeof(bool)*(2*n+1));

  19. memset(meet[i+n],0,sizeof(bool)*(2*n+1));

  20. meet[i][i+1]=true;

  21. meet[i+1][i]=true;

  22. meet[i+n][(i+n)%2+1]=true;

  23. meet[(i+n)%2+1][i+n]=true;

  24. for(int j=1;j<=n;j++)

  25. std::cin>>a[i][j];

  26. }

  27. for(int k=1;k<=2*n;k++)

  28. for(int i=1;i<=2*n;i++)

  29. for(int j=1;j<=2*n;j++)

  30. {

  31. if((i<k)&&(j>k)&&meet[i][k]&&meet[k][j]&&a[(i-1)%n+1][(k-1)%n+1]&&a[(j-1)%n+1][(k-1)%n+1])

  32. {

  33. meet[i][j]=true;

  34. meet[j][i]=true;

  35. }

  36. }

  37. ans=0;

  38. for(int i=1;i<=n;i++)

  39. {

  40. if(meet[i][i+n])

  41. ans++;

  42. }

  43. std::cout<<ans<<std::endl;;
  44. }

  45. private:

  46. int n;

  47. int **a;

  48. bool **meet;

  49. int ans;

  50. };

  51. #endif

11, 科学家实验陨石

总共获得了n1块A星的和n2块B星的,每次实验需要各一块,实验完了后不能回收。

总共实验min(n1,n2);

求每次试验的绝对值和的最小值

先证明一下两个递增序列,a1<b1,a2<b2,  |a2-a1|+|b2-b1|<=|b2-a1|+|b1-a2|

------a1----------------b1-------------

- -

-

- O -

------a2----------------b2--------------

由上图根据两条边之和大于第三边可以直接证明上面的式子

所以就可以放心的使用动态规划了

对两列数据由小到大排序,少的用i表示,多的用j表示

F[i,j]=min(F[i,j-1],F[i-1][j-1]+dis(i,j))      j>i

F[i-1][j-1]+dis(i,j)     j=i

编程暂时省略,因为这个题目比较简单

12,理想收入问题

只有一元的本金,知道每天的股价为v[i], 求M天之后的理想收入,能够获得的最大的收入

假设第i天的理想收入为F[i]

则有F[i]=max(F[j]/v[k])*v[i]; j<=k<i

令M[i]=max(F[j]/v[k])=max(M[i-1],MF[i-1]/v[i]);M[i]表示在第i天可以得到的最多股票数,MF[i-1]表示前i-1天可以取得的最大收入

MF[i]=Max(MF(i-1),F(i-1))

13. 一般的RMQ问题 (Range Minimum/Maximum
Query)

对于给定的N与N个整数A[1...N],M与M对下标(x,y)(x<=y),对于每对下标(x,y)求出A[x...y]中最小数的下标

解法:

预处理,对于区间中每一个点A,设计以A为左端点的floor(logN)+1个区间

[A,A+2^0-1], [A,A+2^1-1],...,[A, A+2^floor(logN)-1]

在求解的过程中,按照区间长度依次求解。[A,A+2^(i+1)-1],可以通过两个等长的子区间[A,A+2^i-1]和[A+2^i,A+2^(i+1)-1];

取用方法:

A,当区间的重叠对结果无影响的时候,例如求最大最小值(本题所述,则根据区间[A,A+2^k-1]与[B-2^k+1,B]来计算

B,当区间的重叠对结果有影响的时候,例如统计数字的多少等等,将[A,B]划分为[A,A+2^k-1],

[A+2^K,A+2^k+2^(floor(log(B-(A+2^k)+1)))-1],每次使用对数直接获得划分点。所以至多分成了floor(log(B-A+1))+1个区间。

所以这样处理的时间复杂度为logN

本题中,所述的所有对数,均是以2为底。

本题中,开一个数组,行下标表示区间开始端A,列坐标表示区间结束端B

[cpp] view plaincopy

  1. #include "stdafx.h"

  2. #include <iostream>

  3. #include <math.h>

  4. using namespace std;

  5. class RMQProblem

  6. {

  7. int N;

  8. int M;

  9. int *data;

  10. int **b;

  11. public:

  12. RMQProblem()

  13. {

  14. cin>>N>>M;

  15. data=new int[N+1];

  16. b=new int*[N+1];

  17. int tempRankNum=int(floor(log2(double(N))))+1;

  18. for(int i=1;i<=N;i++)

  19. {

  20. cin>>data[i];

  21. b[i]=new int[tempRankNum];

  22. b[i][0]=data[i];

  23. }

  24. for(int j=1;j<=tempRankNum;j++)

  25. {

  26. int k=1<<j;

  27. int kk=1<<(j-1);

  28. for(int i=1;i<=N-k+1;i++)

  29. {

  30. b[i][j]=min(b[i][j-1],b[i+kk][j-1]);

  31. }

  32. }
  33. int tempX,tempY,tempZ;

  34. for(int i=1;i<=M;i++)

  35. {

  36. cin>>tempX>>tempY;

  37. tempZ=int(floor(log2(tempY-tempX)));

  38. cout<<min(b[tempX][tempZ],b[tempY-int(1<<tempZ)][tempZ]);
  39. }

  40. }

  41. double log2(double x)

  42. {
  43. return log(double(x))/log(2.0);

  44. }

  45. int min(int x,int y)

  46. {

  47. return x<y?x:y;

  48. }

  49. };

14, 关于分石子问题的动态规划解法

有n个石头,k个框子。把n个石头按顺序放入k个框子,比如1~3放入1,4~6放入2,要求最大重量的框子的重量最小的放法。

设石子的重量分别为Q1,Q2,...

g(i,j)=Qi+,...,+Qj;

f(i,j)表示把i个石子放到j个框的最大重量框的重量。

则f(i,j)=minj-1<=p<i (f(i,j),max(f(p,j-1),g(p+1,i)));

g(i,i)=Qi ,f(1,1)=g(1,1),f(i,1)=g(1,i);

1<=i<=n;

1<=j<=k;

如果提前把Si =Q1+,..,+Qi
计算出来,g(j,k)=Sk -Sj-1 则时间复杂度为O(N2 )

http://blog.csdn.net/duxingstar/article/details/6032411

动态规划题目,布布扣,bubuko.com

时间: 2024-10-19 23:29:41

动态规划题目的相关文章

poj 动态规划题目列表及总结

此文转载别人,希望自己能够做完这些题目! 1.POJ动态规划题目列表 容易:1018, 1050, 1083, 1088, 1125, 1143, 1157, 1163, 1178, 1179, 1189, 1208, 1276,1322, 1414, 1456, 1458, 1609, 1644, 1664, 1690, 1699, 1740(博弈),1742, 1887, 1926(马尔科夫矩阵,求平衡), 1936, 1952, 1953, 1958, 1959, 1962, 1975,

[SinGuLaRiTy] 动态规划题目复习

[SinGuLaRiTy-1026] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. [UVA 1025] A Spy in the Metro 题目描述 特工玛利亚被送到S市执行一个特别危险的任务.她需要利用地铁完成他的任务,S市的地铁只有一条线路运行,所以并不复杂. 玛利亚有一个任务,现在的时间为0,她要从第一个站出发,并在最后一站的间谍碰头.玛利亚知道有一个强大的组织正在追踪她,她知道如果一直呆在一个车站,她会有很大的被抓的风险,躲

动态规划题目(三)——最大连续乘积子串

动态规划题目(三)--最大连续乘积子串 1. 题目描述 给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8.也就是说,上述数组中,3 0.5 8这3个数的乘积30.58=12是最大的,而且是连续的. 2. 动态规划求解 动态规划求解题目的时候最重要的是要找到状态转移方程! 针对这道题目,我们使用两个变量记录当前最大值maxEnd, 和当前最小值minEnd.为什么记录当前最小值呢?因为数组中会出现负数,乘以一个负数的话

动态规划题目(一)——换零钱

动态规划题目(一)--换零钱 1. 题目描述 想兑换100元钱,有1,2,5,10四种钱,问总共有多少兑换方法. 下面提供两种实现方式,其中代码注释的很清楚. 关于动态规划的基本原理,参考: http://www.cnblogs.com/sdjl/articles/1274312.html 2. 递归解法 //动态规划 #include<iostream> using namespace std; const int N = 100; int dimes[] = {1, 2, 5, 10};

动态规划题目(二)——跳台阶

动态规划题目(二)--跳台阶 1. 题目描述 一个台阶总共有n 级,如果一次可以跳1 级,也可以跳2 级. 求总共有多少总跳法,并分析算法的时间复杂度. 2. 递归方式 对于这个动态规划问题,我们一样分两步来想: 假如我们跳了1级,那么剩下的跳法就是f(n-1): 假如我们跳了2级,那么剩下的跳法就是f(n-2): 这个时候我们就可以递归实现了,慢!我们还需要跳出递归的条件,这个也是必不可少的! 这个题目而言,跳出递归的条件是当n==1, 2的时候,我们返回1, 2. 为什么是1,2呢?因为当n

一道简单的动态规划题目——House Robber

一.题目 House Robber(一道Leetcode上的关于动态规划的简单题目)具体描述如下: There is a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent hous

有关货币问题的动态规划题目--有关01背包,完全背包,多重背包

背包dp:参考背包九讲以及给出一些题目 01背包 (先枚举物品,再逆序枚举容量) 给定n件物品和一个容量为V的背包,每件物品的体积是w[i],价值是va[i](1<=i<=n),求在不超过背包的容量的情况下,怎么选择装这些物品使得得到的价值最大? 例如:有5件物品,体积分别是{2,2,6,5,4},价值分别是{6,3,5,4,6} 递归式:F(i,v)=max(F(i-1,v), F(i-1,v-w[i])+va[i]),其中F(i,v)表示把前i种物品恰放入背包容量为v时取得的最大价值 把这

[DynamicProgramming]动态规划题目泛做

Educational Codeforces Round 12 F 大意: 求n(n<=1011)以内恰好有4个因数的数的个数 分析: 首先一个数恰好有4个因数,说明它质因数分解之后是两个质数的乘积或是一个质数的三次方,对于后一种情况我们直接n1/3就能算出来,关键在于计算n以内有多少个数是两个素数的乘积. 设n=p1?p2,则必然有p1<n?√,p2>n?√,我们枚举p1,那么问题就在于np1 内有多少个素数. 这一点我们用dp来解决. 记pj为第j个素数,dp[n][j]为[1,n]

动态规划学习中的一道题目

记得初中时候的一篇文章说,做学问,必须要有学和问两个方面. 做了一些动态规划题目之后偶然看到这个介绍动态规划的文章.然后遇见第一道题目就卡壳. 经过反思文章以及自己思考,总算对题目有了一些理解.下面分享给大家,初学者们看到这个题目不至于太过茫然——像我一样. 工厂生产某种产品,每单位(千件)的成本为1(千元),每次开工的固定成本为3(千元),工厂每季度的最大生产能力为6(千件).经调查,市场对该产品的需求量第一.二.三.四季度分别为        2,3,2,4(千件).如果工厂在第一.二季度将