动态规划算法。在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
- #include <iostream>
- #define MAXLen 1000
- char seq1[MAXLen];
- char seq2[MAXLen];
- int maxLen[MAXLen][MAXLen];
- int MaxCommonMain()
- {
- while(scanf("%s%s",seq1+1,seq2+1)>0)
- {
- int length1=strlen(seq1+1);
- int length2=strlen(seq2+1);
- for(int i=0;i<=length1;i++)
- maxLen[i][0]=0;
- for(int j=0;j<=length2;j++)
- maxLen[0][j]=0;
- for(int i=1;i<=length1;i++)
- {
- for(int j=1;j<=length2;j++)
- {
- if(seq1[i]==seq2[j])
- maxLen[i][j]=maxLen[i-1][j-1]+1;
- else
- maxLen[i][j]=maxLen[i-1][j]>maxLen[i][j-1]?maxLen[i-1][j]:maxLen[i][j-1];
- }
- }
- printf("%d/n",maxLen[length1][length2]);
- // printf("%d",maxLen[length2-1][length1-1]);
- }
- return 0;
- }
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
- #include<stdlib.h>
- #include<iostream>
- #include<memory.h>
- int p[300];
- int d[300];
- int path[30][1000];
- int f[30][1000];
- int result[30];
- int compareInt(const void *el, const void *e2)
- {
- return *((int *)el)-*((int *)e2);
- }
- int jurymain()
- {
- int n,m;
- scanf("%d%d",&n,&m);
- int no=0;
- int i=0;
- while(n+m)//n=0,m=0结束条件
- {
- no++;
- for(i=1;i<=n;i++)
- scanf("%d%d",&p[i],&d[i]);
- memset(f,-1,sizeof(f));
- memset(path,0,sizeof(path));
- int minD=20*m;
- f[0][minD]=0;
- for(int j=0;j<m;j++)
- {
- for(int k=0;k<=minD*2;k++)
- {
- if(f[j][k]>=0)
- {
- i=1;
- int temp1;
- int temp2;
- for(;i<=n;i++)
- {
- if(f[j][k]+p[i]+d[i]>f[j+1][k+p[i]-d[i]])
- {
- temp1=j;
- temp2=k;
- while(temp1>0&&path[temp1][temp2]!=i)
- {
- temp2=temp2-p[path[temp1][temp2]]+d[path[temp1][temp2]];
- temp1--;
- }
- if(temp1==0)
- {
- f[j+1][k+p[i]-d[i]]=f[j][k]+p[i]+d[i];
- path[j+1][k+p[i]-d[i]]=i;
- }
- }
- }
- }
- }
- }
- i=minD;
- int j=0;
- int k=0;
- while(f[m][i+j]<0&&f[m][i-j]<0)
- j++;
- if(f[m][i+j]>f[m][i-j])
- k=i+j;
- else
- k=i-j;
- printf("Jury #%d/n",no);
- 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);
- for(i=1;i<=m;i++)
- {
- result[i]=path[m-i+1][k];
- k-=p[result[i]]-d[result[i]];
- // std::cout<<"result "<<i<<" "<<result[i]<<std::endl;
- }
- qsort(result+1,m,sizeof(int),compareInt);
- for(i=1;i<=m;i++)
- printf("%d ",result[i]);
- printf("/n/n");
- scanf("%d%d",&n,&m);
- }
- //system("pause");
- return 0;
- }
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
- #include <stdlib.h>
- #include <stdio.h>
- void getMaxValue(int **f,int **v,int m,int n)
- {
- for(int j=1;j<=m;j++)
- {
- for(int i=1;i<=j-1;i++)
- {
- if(f[i-1][j-1]+v[i][j]>f[i][j-1])
- f[i][j]=f[i-1][j-1]+v[i][j];
- else
- f[i][j]=f[i][j-1];
- }
- }
- }
- int main()
- {
- int n,m;
- scanf("%d%d",&n,&m);
- int **v=new int[n+1][m+1];
- int **f=new int[n+1][m+1];
- v[0][0]=0;
- for(int i=1;i<=n;i++)
- for(int j=1;j<=m;j++)
- scanf("$d",v[i][j]);
- for(int j=0;j<=m;j++)
- v[0][j]=0;
- int tempSum=0;
- for(int i=0;i<=n;i++)
- {
- sum+=v[i][i];
- f[i][i]=sum;
- }
- getMaxValue(f,v,m,n);
- delete(v);
- return 0;
- }
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
- #ifndef TICKETAWARD_H
- #define TICKETAWARD_H
- class TicketAward
- {
- int *b;//度数
- int **g;//g(i,j)表示第i个点的第j个孩子
- int **f;//函数f
- int n;//总共同拥有n个节点
- public:
- TicketAward();
- int max(int x,int y);
- };
- #endif
[cpp] view plain copy
- #include "stdafx.h"
- #include <iostream>
- #include "TicketAward.h"
- TicketAward::TicketAward()
- {
- std::cin>>n;
- g=new int *[n+1];
- b=new int[n+1];
- f=new int*[n+1];
- for(int i=1;i<=n;i++)
- {
- g[i]=new int[n+1];
- f[i]=new int[n+1];
- b[i]=0;
- for(int j=1;j<=n;j++)
- {
- }
- }
- int temp=0;
- for(int i=1;i<=n;i++)
- {
- std::cin>>temp;//输入i的第一个孩子。假设不为0,即就是存在孩子节点,则继续
- while(temp!=0)
- {
- b[i]++;
- g[i][b[i]]=temp;
- std::cin>>temp;
- }
- }
- f[1][1]=0;
- for(int i=1;i<=n;i++)
- {
- for(int j=1;j<=b[i];j++)
- {
- if(i==j==1)
- continue;
- if(i<j)
- for(int k=1;k<=b[i];k++)
- {
- f[i][j]=max(f[i][j],(f[g[i][k]][j]+1));
- }
- if(i>j)
- for(int k=1;k<=b[j];k++)
- {
- f[i][j]=max(f[i][j],(f[g[j][k]][j]+1));
- }
- }
- }
- std::cout<<(f[n][n]-2);
- }
- int TicketAward::max(int x, int y)
- {
- return x>y?x:y;
- }
5, 最长词链问题
给定一个只包括小写字母的英文单词表。当中每一个单词至少包括一个字母,最多包括75个字母。且依照字典序排列。
全部单词包括的单词个数之和不超过200w
假设在一张由一个单词或者多个单词组成的表中。每一个单词都被其后的一个单词所包括。则成此表为一个链。
如今要求从单词表中找到一个包括单词书最多的最长词链。
该问题能够由动态规划解。
这让我想起了一个题:在一个连续的长字符串中。找出匹配次数最多的一个词组,不能有重叠。同样的方法。
令F(k)表示第k个单词的时候的最长的链。
则F(k)=maxF(i)+1。i从1,到k-1,而且第i个单词是第k个的前缀。这儿问题就解了。
可是,对于这道题还有其它的解法。
由于字典序并不是乱序。每一个单词的前一个单词所包括的前缀非常可能就是这个单词的前缀,而且前一个单词也可能就是该单词的前缀。由于前缀的特殊性。所以仅仅用检查前一个单词的前缀(包括该单词本身)就能找出该前缀链
代码为非动态规划算法
[cpp] view plain copy
- #include "stdafx.h"
- #ifndef MOSTLENGTHLETTER_H
- #define MOSTLENGTHLETTER_H
- #include <iostream>
- #include <string>
- struct Pnode
- {
- int len;
- Pnode *next[26];
- };
- struct MostLengthLetters
- {
- public:
- MostLengthLetters()
- {
- for(int i=0;i<=25;i++)
- {
- root.next[i]=NULL;
- }
- ans=0;
- std::cout<<"please input the num of words"<<std::endl;
- std::cin>>wordsNum;
- this->getWords();
- }
- void getWords();
- void update(std::string temp);
- int wordsNum;
- Pnode root;
- int ans;
- };
- void MostLengthLetters::getWords()
- {
- std::string temp;
- for(int i=0;i<this->wordsNum;i++)
- {
- std::cin>>temp;
- this->update(temp);
- }
- }
- void MostLengthLetters::update(std::string temp)
- {
- int len=temp.length();
- Pnode p=root;
- int max=0;
- int i=1;
- int tempInt=0;
- while(i<len)
- {
- tempInt=temp[i]-'a';
- if(p.next[i]==NULL)
- {
- Pnode *node=new Pnode;
- node->len=0;
- for(int j=0;j<=25;j++)
- {
- node->next[j]=NULL;
- }
- }
- if(max<p.len)
- max=p.len;
- i++;
- p=*p.next[tempInt];
- }
- p.len=max+1;
- this->ans=p.len;
- return;
- }
- #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
- #include "stdafx.h"
- #ifndef SPLITTOKNUM_H
- #define SPLITTOKNUM_H
- #include<iostream>
- #include <memory.h>
- #define MaxN 100
- class SplitToKNum
- {
- public:
- SplitToKNum()
- {
- std::cin>>n;
- std::cin>>k;
- memset(f,0,sizeof(f));
- for(int i=1;i<=k;i++)
- {
- f[MaxN][i]=1;
- }
- for(int j=1;j<=k;j++)
- for(int i=MaxN+1;i<=MaxN+n;i++)
- f[i][j]=f[i][j-1]+f[i-j][j];
- std::cout<<f[n+MaxN][k]-f[n+MaxN][k-1]<<std::endl;
- }
- int n;
- int k;
- int f[2*MaxN+1][MaxN];
- };
- #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
- #include <stdlib.h>
- #include <stdio.h>
- #include <iostream>
- #define MAXN 1000
- double x[MAXN];// x radix
- double y[MAXN];
- double dis[MAXN][MAXN];
- int n;// the num of
- double f[MAXN][2][2];
- double min(double a,double b)
- {
- return a<b?a:b;
- }
- int main()
- {
- std::cout<<"please input the num of the points"<<std::endl;
- std::cin>>n;
- for(int i=0;i<n;i++)
- {
- std::cin>>x[i]>>y[i];
- }
- for(int i=0;i<n-1;i++)
- {
- for(int j=i+1;j<n;j++)
- {
- dis[i][j]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
- dis[j][i]=dis[i][j];
- }
- }
- int sign=0;
- double ans=1000000;
- for(int j=1;j<=n;j++)
- {
- sign=1-sign;
- for(int i=n-1;i>=0;i--)
- {
- if(j==1)
- {
- f[i][sign][0]=0;
- f[i][sign][1]=0;
- }
- else
- {
- 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]);
- 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]);
- }
- if(j==n)
- ans=min(ans,f[i][sign][1]);
- }
- }
- //std::cout<<f[1][sign][1]<<"/t"<<f[1][sign][0]<<std::endl;
- std::cout<<ans<<std::endl;
- return 0;
- }
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
- #include <stdlib.h>
- #include <iostream>
- #include <memory.h>
- class RankProgram
- {
- public:
- RankProgram()
- {
- std::cout<<"please input the testing data"<<std::endl;
- std::cin>>n>>p;
- s=new int[n+2];
- g=new int* [n+2];
- for(int i=0;i<=n+1;i++)
- {
- g[i]=new int[n+2];
- memset(g[i],0,sizeof(int)*(n+2));
- g[i][1]=1;
- }
- memset(s,0,sizeof(int)*(n+2));
- int tempData;
- int tempP;
- for(int i=1;i<=p;i++)
- {
- std::cin>>tempP>>tempData;
- s[n-tempP+1]=tempData;
- }
- for(int i=n;i>=1;i--)
- {
- for(int j=2;j<=n-i+1;j++)
- {
- if(s[j]==i)
- g[i][j]=g[i+1][j-1];
- else if(s[j]==(i+j-1))
- g[i][j]=g[i][j-1];
- else
- g[i][j]=g[i+1][j-1]+g[i][j-1];
- }
- }
- std::cout<<g[1][n]<<std::endl;
- }
- ~RankProgram()
- {
- delete [] s;
- for(int i=0;i<=n+1;i++)
- delete [] g[i];
- delete [] g;
- }
- private:
- int *s;
- int **g;
- int p;
- int n;
- };
- int main()
- {
- RankProgram rp;
- }
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
- #ifndef MATRIXMOVE_H
- #define MATRIXMOVE_H
- #include <iostream>
- #include <memory.h>
- #define MAXN 101
- class MatrixMove
- {
- public:
- MatrixMove()
- {
- std::cin>>n;
- memset(f,0,sizeof(f));
- f[n+1][0][0]=1;
- f[n+1][0][1]=f[n+1][1][0]=f[n+1][1][1]=0;
- std::cin>>x>>y;
- for(int i=1;i<=n;i++)
- {
- p[n-i+1]=x%2;
- q[n-i+1]=y%2;
- x=x/2;
- y=y/2;
- }
- for(int i=n+1;i>=2;i--)
- {
- for(int k1=0;k1<=1;k1++)
- for(int k2=0;k2<=1;k2++)
- for(int a=0;a<=1;a++)
- for(int b=0;b<=1;b++)
- {
- if(!(a==0&&b==1))
- {
- int kk1=(a+p[i-1]+k1)/2;
- int kk2=(b+q[i-1]+k2)/2;
- if(!((a+p[i-1]+k1)%2==0&&(b+q[i-1]+k2)%2==1))
- {
- f[i-1][kk1][kk2]+=f[i][k1][k2];
- }
- }
- }
- }
- std::cout<<f[1][0][0]<<std::endl;
- }
- private:
- int n;
- int x;
- int y;
- int f[MAXN][2][2];
- int p[MAXN];
- int q[MAXN];
- };
- #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
- #ifndef ACIENTSOLDIER_H
- #define ACIENTSOLDIER_H
- #include <iostream>
- #include <memory.h>
- class AcientSoldier
- {
- public:
- AcientSoldier()
- {
- std::cin>>n;
- a=new int*[n+1];
- meet=new bool*[2*n+1];
- for(int i=1;i<=n;i++)
- {
- a[i]=new int[n+1];
- meet[i]=new bool[2*n+1];
- meet[i+n]=new bool[2*n+1];
- memset(meet[i],0,sizeof(bool)*(2*n+1));
- memset(meet[i+n],0,sizeof(bool)*(2*n+1));
- meet[i][i+1]=true;
- meet[i+1][i]=true;
- meet[i+n][(i+n)%2+1]=true;
- meet[(i+n)%2+1][i+n]=true;
- for(int j=1;j<=n;j++)
- std::cin>>a[i][j];
- }
- for(int k=1;k<=2*n;k++)
- for(int i=1;i<=2*n;i++)
- for(int j=1;j<=2*n;j++)
- {
- 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])
- {
- meet[i][j]=true;
- meet[j][i]=true;
- }
- }
- ans=0;
- for(int i=1;i<=n;i++)
- {
- if(meet[i][i+n])
- ans++;
- }
- std::cout<<ans<<std::endl;;
- }
- private:
- int n;
- int **a;
- bool **meet;
- int ans;
- };
- #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
- #include "stdafx.h"
- #include <iostream>
- #include <math.h>
- using namespace std;
- class RMQProblem
- {
- int N;
- int M;
- int *data;
- int **b;
- public:
- RMQProblem()
- {
- cin>>N>>M;
- data=new int[N+1];
- b=new int*[N+1];
- int tempRankNum=int(floor(log2(double(N))))+1;
- for(int i=1;i<=N;i++)
- {
- cin>>data[i];
- b[i]=new int[tempRankNum];
- b[i][0]=data[i];
- }
- for(int j=1;j<=tempRankNum;j++)
- {
- int k=1<<j;
- int kk=1<<(j-1);
- for(int i=1;i<=N-k+1;i++)
- {
- b[i][j]=min(b[i][j-1],b[i+kk][j-1]);
- }
- }
- int tempX,tempY,tempZ;
- for(int i=1;i<=M;i++)
- {
- cin>>tempX>>tempY;
- tempZ=int(floor(log2(tempY-tempX)));
- cout<<min(b[tempX][tempZ],b[tempY-int(1<<tempZ)][tempZ]);
- }
- }
- double log2(double x)
- {
- return log(double(x))/log(2.0);
- }
- int min(int x,int y)
- {
- return x<y?x:y;
- }
- };
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 )