关于动态规划算法的总结

动态规划算法。在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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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
plain
copy

  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 )

时间: 2024-08-14 00:29:41

关于动态规划算法的总结的相关文章

01背包问题的动态规划算法

01背包问题我最初学会的解法是回溯法,第一反应并不是用动态规划算法去解答.原因是学习动态规划算法的时候,矩阵连乘.最长公共子串等问题很容易将问题离散化成规模不同的子问题,比较好理解,而对于01背包问题则不容易想到将背包容量离散化抽象出子问题,从情感上先入为主也误以为动态规划算法不是解决01背包问题的好方法,实际上并不是这样的.另外,动态规划算法不对子问题进行重复计算,但是要自底向上将所有子问题都计算一遍,直到计算出最终问题的结果也就是我们要的答案,有点像爬山的感觉. 问题描述:给定n种物品和一背

动态规划分析总结——如何设计和实现动态规划算法

进行算法设计的时候,时常有这样的体会:如果已经知道一道题目可以用动态规划求解,那么很容易找到相应的动态规划算法并实现:动态规划算法的难度不在于实现,而在于分析和设计-- 首先你得知道这道题目需要用动态规划来求解.本文,我们主要在分析动态规划在算法分析设计和实现中的应用,讲解动态规划的原理.设计和实现.在很多情况下,可能我们能直观地想到动态规划的算法:但是有些情况下动态规划算法却比较隐蔽,难以发现.本文,主要为你解答这个最大的疑惑:什么类型的问题可以使用动态规划算法?应该如何设计动态规划算法? 动

[LeetCode] 时间复杂度 O(n),空间复杂度 O(1) 的动态规划算法,题 Jump Game

Jump Game Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position. Determine if you are able to reach the last index. For ex

五种常用算法之二:动态规划算法

动态规划算法: 基本思想: 动态规划算法通常用于求解具有某种最优性质的问题.在这类问题中,可能会有许多可行解.每一个解都对应于一个值,我们希望找到具有最优值的解.动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的.若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次.如果我们能够保存已解决的子问题的答案,而在需要时再找

动态规划分析总结——怎样设计和实现动态规划算法

进行算法设计的时候,时常有这种体会:假设已经知道一道题目能够用动态规划求解,那么非常easy找到对应的动态规划算法并实现:动态规划算法的难度不在于实现,而在于分析和设计-- 首先你得知道这道题目须要用动态规划来求解. 本文,我们主要在分析动态规划在算法分析设计和实现中的应用,解说动态规划的原理.设计和实现.在非常多情况下,可能我们能直观地想到动态规划的算法.可是有些情况下动态规划算法却比較隐蔽.难以发现. 本文.主要为你解答这个最大的疑惑:什么类型的问题能够使用动态规划算法?应该怎样设计动态规划

动态规划算法之滚动数组的求解(C++)

虽然接触动态规划算法已经有一段时间,给一个01背包问题,能够做到一个表格简单粗暴下去,然后求得结果,但心里总觉得对这个算法理解十分不到位,抱着对算法的热爱,网上很多大牛的算法思维实在让我佩服的五体投地.在此讲一讲动态规划中滚动数组的求解方法,算是对这个知识点做一个记录,也希望有写的不妥的地方,大家能不吝赐教. 首先,我们先看看"滚动数组"的例题,大家可以参考http://www.lintcode.com/en/problem/house-robber/ 题意大概就是说:一个盗贼要去偷盗

动态规划算法的理解

什么是动态规划算法? 动态规划算法其实质就是分治思想和解决冗余.因此它与分治法和贪心法类似,都是将待求解问题分解为更小的,相同的子问题,然后对子问题进行求解,最终产生一个整体最优解. 适合采用动态规划法求解的问题,经分解得到的各个子问题往往不是相互独立的.在求解过程中,将已解决的子问题的解进行保存,在需要时可以轻松地找出. 示例如下: Fibonacci数列       0   n=0 f(n)=  1   n=1 f(n-1)+f(n-2)    n>1 如果n=4,则f(4)=f(3)+f(

动态规划 算法(DP)

多阶段决策过程(multistep decision process)是指这样一类特殊的活动过程,过程可以按时间顺序分解成若干个相互联系的阶段,在每一个阶段都需要做出决策,全部过程的决策是一个决策序列.动态规划(dynamic programming)算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强.利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题.动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子

动态规划算法--01背包问题

基本思想: 动态规划算法通常用于求解具有某种最优性质的问题.在这类问题中,可能会有许多可行解.每一个解都对应于一个值,我们希望找到具有最优值的解.动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解.与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解).若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很

动态规划算法之:最长公共子序列 & 最长公共子串(LCS)

1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab") b a b c 0 0 0 a 0 1