在讨论今天的问题之前,我先扯两句我学习算法的一点感想。我觉得算法的学习主要可以分为三个阶段,而我目前处于第二阶段并在此阶段缓慢而又努力的前行着。第一阶段就是系统,完整的学习阶段,这一阶段我们在算法课上就完成了。为了将这三个阶段表现的更形象,我用以下三句来表示:
第一阶段:算法一入深似海,从此代码是路人。
第二阶段:代码算法真是难,而今迈步从头阅。
第三阶段:众里寻他千百度,蓦然回首,他俩却在灯火阑珊处。
第二阶段就是有了具体的问题却不知该用哪个算法解决,而且这个阶段还有一个问题就是如果编码能力不济,即使有了算法,编程实现仍然会有很大的困难,而我就属于如果那种,在第二阶段那更是痛苦。第三阶段的感受是我猜的,就是算法代码能力都已具备,就等恍然大悟那一刻的到来。
我第二阶段首选的目标是动态规划,因此就研究了几道题,下面就关于这几道题谈谈用动态规划的解法。(其它方法也可解而且也可能更高效,本文是为了研究限于动态规划的思想)(前两道来自wikioi)
1160 蛇形矩阵
题目描述 :小明玩一个数字游戏,取个n行n列数字矩阵(其中n为不超过100的奇数),数字的填补方法为:在矩阵中心从1开始以逆时针方向绕行,逐圈扩大,直到n行n列填满数字,请输出该n行n列正方形矩阵以及其的对角线数字之和.
#include<iostream>
int main()
{
int n;
std::cin>>n;
int ** a=new int*[n];
for(int i=0;i<n;i++)
a[i]=new int[n];
for(int i=1;i<=n;i=i+2)
{
for(int j=i-1;j>1;j--)
for(int k=i-1;k>1;k--)
a[j-1][k-1]=a[j-2][k-2];
int temp=i*i-2*i+2;
for(int m=0;m<2*i-1;m++)
{
if(m>=i)
a[i-1][m-i+1]=temp+m;
else a[m][0]=temp+m;
}
for(int m=0;m<2*i-3;m++)
{
if(m>=i-1)
a[m-i+2][i-1]=temp-m-1;
else a[0][m+1]=temp-m-1;
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
std::cout<<a[i][j]<<" ";
std::cout<<std::endl;
}
int result=0;
for(int i=0;i<n;i++)
if(i==(n-1)/2)
result+=a[i][i];
else
{
result+=a[i][i];
result+=a[i][n-1-i];
}
std::cout<<result;
return 0;
}
以上代码可以在wikioi平台AC,不过显示的格式由于没有调整,看起来有点别扭(主要是加上格式调整后就不能AC了)。
1039 数的划分
题目描述:将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种划分方案被认为是相同的。1 1 5 1 5 1 5 1 1 问有多少种不同的分法。
(图片粘自http://blog.csdn.net/fisher_jiang/article/details/813314)
看到这两幅图片不知你有没有什么思路?第一幅图可以表示为:10=1+1+3+5 第二幅图可以表示为:10=4+2+2+1+1
如果对于这道题n=10,k=4;那第一幅图就是一个解,第二幅图与第一幅图有什么联系呢?将第二幅图逆时针旋转90度即可成为第一幅图,而第二幅图的拆分方法却是n的任意拆分,只不过最大元素只能是k而已。由此我们可以想到,将n先做任意拆分,拆分的最大元素限制必须为k,然后将拆分图逆时针旋转90度,就成为了n的k拆分了。(那么我们找出n的最大元素为k任意拆分的方案数后也就找到了n的k拆分),大致意思就是这样。因此问题就转为找出n的最大元素为k任意拆分的方案数。
下面是该方法的地推关系式的理论推导:
用f (a,b)表示把b做任意份划分,其中最大的一个部分等于a的方案总数,用g(a,b)表示把b做任意份划分,其中最大的一个部分不大于a的方案总数,
所以有f (a,b)=g (a,b-a); 解释一下这个等式:f(a,b)表示上图中右图,将b分成最大元素为a的划分。g(a,b-a)表示先分出一个a,然后对b-a做不大于a的任意划分,所以两式表示的意义是一样的。
并且有: g(a,b)=f(1,b)+f(2,b)+...f(a,b); 解释一下就是b做不大于a的任意划分,正好是b做最大元素分别1,2,3....a的任意划分,因此两式表示的意思也是一样的。
由该式进一步推出f(1,b)+f(2,b)+..f(a-1,b) =g(a-1,b)。
所以,综合上面几式有:g(a,b)=f(1,b)+...f(i,b)+f(a,b) (1<=i<=a-1)
=g(a-1,b)+g(a,b-a)
我们求出所有的g(a,b),最后g(k,n-k)即为所求。
该递推关系初始化值:b<a时,g(a,b-a)无意义。当a=1时, g(1,b)=1。
你可能会疑问动态规划在哪呢?就是上面那个递推关系式,每次的结果都是基于前面的结果来求解。
题解(二):第二种题解方法从不同的角度来寻找上式的递推关系式,该思路理解起来比较简单,但原理基本还是同上题解。
我们是要对n进行k划分,我们可以这样来分解子问题,分为含有1和不含有1,含有1就为f(a-1,b-1)表示的意思就是至少a份里有一份是1,不含有1就为f(a,b-a)表示的意思就是首先给a份没分都分一个1,然后剩余b-a,再把b-a进行a份划分,则a份划分就不含1。
那么,递推关系式就为f(a,b)=f(a-1,b-1)+f(a,b-a)。
下面是根据第二种题解给出的代码,(已经在wikioi上AC)
#include<iostream>
int main()
{
int n,k;
std::cin>>n>>k;
int ** a=new int* [k+1];
for(int i=0;i<=k;i++)
a[i]=new int[n+1];
for(int i=0;i<n+1;i++)
{
a[0][i]=0;
a[1][i]=1;
}
for(int i=2;i<=k;i++)
for(int j=0;j<i;j++)
a[i][j]=0;
for(int i=2;i<=k;i++)
for(int j=i;j<=n;j++)
{
a[i][j]=a[i][j-i]+a[i-1][j-1];
}
std::cout<<a[k][n];
}