动态规划整理(两)

       这一篇博客继续以一些OJ上的题目为载体。对动态规划专题进行整理一下。

会陆续的更新。。。

这是动态规划专题整理的第二篇博客,第一篇博客的地址在:http://blog.csdn.net/hjd_love_zzt/article/details/26979313


三、其它一些使用DP来解决的题目


1、HDU 2084

题目与分析:

使用DP来解决的题目具有的一个特点就是:问题的最优解包括了子问题的最优解。

  
    
从顶部出发。在每一结点能够选择向左走或是向右走,一直走究竟层,要求找出一条路径,使路径上的值最大。
从顶点出发时究竟向左走还是向右走应取决于是从左走能取到最大值还是从右走能取到最大值,仅仅要左右两道路径上的最大值求出来了才干作出决策。

相同,下一层的走向又要取决于再下一层上的最大值是否已经求出才干决策。这样一层一层推下去。直到倒数第二层时就非常明了。 如数字2,仅仅要选择它以下较大值的结点19前进就能够了。所以实际求解时,可从底层開始,层层递进。最后得到最大值。

操作,先记录最后一行的DP值。然后从倒数第二行開始向上開始计算取最大值,那么根的的值也就最大,最后输出根节点的DP值就能够了。



/*
 * HDU_2084.cpp
 *
 *  Created on: 2014年6月17日
 *      Author: Administrator
 */


#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 105;
int num[maxn][maxn];

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);

		int i;
		int j;
		for(i = 1 ; i <= n ; ++i){
			for(j = 1 ; j <= i ; ++j){
				scanf("%d",&num[i][j]);
			}
		}

		int dp[n+1];
		for(i = 1 ; i <= n ; ++i){//记录最后一行的DP值
			dp[i] = num[n][i];
		}

		for(i = n-1 ; i >= 1 ; --i){//从底向上求最大值
			for(j = 1 ; j <= i ; ++j){
				dp[j] = max(dp[j] + num[i][j],dp[j+1] +num[i][j]);
			}
		}

		printf("%d\n",dp[1]);
	}

	return 0;
}



四、使用DP来解决最长连续子和问题

问题描写叙述:在a[1]、a[2]......a[n]中找到a[i]、a[i+1]、a[j-1]、a[j]使得a[i]+a[i+1]+...+a[j-1]+a[j]最大

①最简单最easy想到的就是依据定义来枚举。
枚举上下界{i,j | 0<=i<=j<=n},维护一个max值就可以。
当中枚举上下界的时间复杂度为O(n^2),求区间和的复杂度为O(n),所以总时间复杂度为O(n^3)。


for ( int i = 1 ; i <= n ; i++ )//i能够理解为区间的起点
    for ( int j = i ; j <= n ; j++ )//j能够理解为区间的终点
        ans = max(ans,accumulate(a+i,a+j+1,0));

②事实上就是第一种方法的优化。


这里有个非常easy想到的优化,即预处理出前缀和sum[i]=a[0]+a[1]+...+a[i-1]+a[i],算区间和的时候就可以将求区间和的复杂度降到O(1),枚举上下界的复杂度不变。所以总时间复杂度为O(n^2)。


for ( int i = 1 ; i <= n ; i++ )
    sum[i]=sum[i-1]+a[i];
for ( int i = 1 ; i <= n ; i++ )
    for ( int j = i ; j <= n ; j++ )
        ans = max(ans,sum[j]-sum[i-1]);


③能够利用动态规划的思维来继续优化,得到一个线性的算法。也是最大连续区间和的标准算法
定义maxn[i]为以i为结尾的最大连续和,则非常easy找到递推关系:maxn[i]=max{0,maxn[i-1]}+a[i]。
所以仅仅须要扫描一遍就可以,总时间复杂度为O(n)。

for ( int i = 1 ; i <= n ; i++ )
{
    last = max(0,last)+a[i];//last是计算到当前这个数的最长连续子序列的和
    ans = max(ans,last);//ans是全局的最场连续子序列的和
}

④相同用到相似的思维。
首先也须要预处理出前缀和sum[i],能够推出ans=max{sum[i]-min{sum[j] } | 0<=j<i<=n }。


而最小前缀和能够动态维护。所以总时间复杂度为O(n)。


for ( int i = 1 ; i <= n ; i++ )
    sum[i]=sum[i-1]+a[i];
for ( int i = 1 ; i <= n ; i++ )
{
    ans = max(ans,sum[i]-minn);
    minn = min(minn,sum[i]);
}

      总结:尽管朴素的O(n^3)和前缀和优化的O(n^2)算法非常easy想到,但代码实现却反而例如法三麻烦。第四个方法尽管有和方法三相同的复杂度,但须要一个预处理和多出的O(n)的空间,所以。方法三非常好非常强大。


例题:

1、NYOJ 44

题目分析:

这道题抽象一下就是给出a[1]...a[n]。求a[i]、a[i+1]...a[j-1]、a[j]。使得a[i]+a[i+1]+...a[j-1]+a[j]最大。输出这个最大值就可以。

这道题是典型的题目。

一下给出算法思想一样(都是採用3中的方法),但可能在实现上有所不同的两种解法.


1)

/*
 * NYOJ_44.cpp
 *
 *  Created on: 2014年6月22日
 *      Author: Administrator
 */

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 1000005;
int a[maxn];


int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);

		int i;
		for(i = 1 ; i <= n ; ++i){
			scanf("%d",&a[i]);
		}

		int last = a[1];
		int ans = a[1];
		for(i = 2 ; i <= n ; ++i){
			last = max(0,last) + a[i];//last: 以a[i]结尾的最长连续子和
			ans = max(last,ans);//ans: 最大的连续子和
		}

		printf("%d\n",ans);
	}

	return 0;
}



2)

/*
 * NYOJ_44.cpp
 *
 *  Created on: 2014年6月22日
 *      Author: Administrator
 */

#include <iostream>
#include <cstdio>

using namespace std;

const int maxn = 1000005;
int a[maxn];

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n;
		scanf("%d",&n);

		int pre;
		int cur;
		scanf("%d",&pre);

		int max = pre;
		int i;
		for(i = 1 ; i < n ; ++i){
			scanf("%d",&cur);

			if((cur+pre) > cur){
				cur += pre;
			}
			if(cur > max){
				max = cur;
			}

			pre = cur;
		}

		printf("%d\n",max);
	}

	return 0;
}






2、NYOJ 104

题目分析:

这道题与上面的不同之处就在于,这道题是求二维的最长子序列连续和,而上面是求一维的最长子序列连续和...本题的关键就在于将二维的问题转换成一味的问题来解决。。

如果最大子矩阵的结果为从第r行到k行、从第i列到j列的子矩阵。例如以下所看到的(ari表示a[r][i],如果数组下标从1開始):
  | a11 …… a1i ……a1j ……a1n |
  | a21 …… a2i ……a2j ……a2n |
  |  .     .     .    .    .     .    .   |
  |  .     .     .    .    .     .    .   |
  | ar1 …… ari ……arj ……arn |
  |  .     .     .    .    .     .    .   |
  |  .     .     .    .    .     .    .   |
  | ak1 …… aki ……akj ……akn |
  |  .     .     .    .    .     .    .   |
  | an1 …… ani ……anj ……ann |
 那么我们将从第r行到第k行的每一行中相同列的加起来,能够得到一个一维数组例如以下:
 (ar1+……+ak1, ar2+……+ak2, ……,arn+……+akn)
 由此我们能够看出最后所求的就是此一维数组的最大子段和问题,到此我们已经将问题转化为上面的已经攻克了的问题了。


代码例如以下:

/*
 * NYOJ_104.cpp
 *
 *  Created on: 2014年6月22日
 *      Author: Administrator
 */


#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 105;
int a[maxn][maxn];
int ans;

int n,m;

/**
 * 求第k行的最长子序列之和
 */
int work(int k){
	int j;

	int last = a[k][1];
	int ansn = a[k][1];
	for(j = 2 ; j <= m ; ++j){
		last = max(0,last) + a[k][j];
		ansn = max(last,ansn);
	}

	return ansn;
}

int main(){

	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);

		int i;
		int j;
		for(i = 1 ; i <= n ; ++i){
			for(j = 1 ; j <= m ; ++j){
				scanf("%d",&a[i][j]);
			}
		}

		ans = -1000;
		int k;
		for(i = 1; i <= n ; ++i){
			ans = max(ans,work(i));//先求该行没有加上以下的行之前的最长子区间
			for(j = i+1 ; j <= n ; ++j){
				for(k = 1 ; k <= m ; ++k){
					a[i][k] += a[j][k];//将二维转换成一维问题的关键..
				}
				ans = max(ans,work(i));
			}

		}

		printf("%d\n",ans);
	}

	return 0;

}



3、POJ 1050

题目分析:

这道题和上面的那一道题是相似的。

仅仅是输入数据不一样而已(矩阵由n*m变成了n*n,其它的还是一样的)...


/*
 * POJ_1050.cpp
 *
 *  Created on: 2014年6月23日
 *      Author: Administrator
 */

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn = 105;

int a[maxn][maxn];

int n;
int ans;

int work(int k){
	int last = a[k][1];
	int ansn = a[k][1];

	int j;
	for(j = 2 ; j <= n ; ++j){
		last = max(0,last) + a[k][j];
		ansn = max(last,ansn);
	}

	return ansn;
}


int main(){
	while(scanf("%d",&n)!=EOF){
		int i;
		int j;
		for(i = 1 ; i <= n ; ++i){
			for(j = 1 ; j <= n ; ++j){
				scanf("%d",&a[i][j]);
			}
		}

		ans = -100000;
		int k;
		for(i = 1 ; i <= n ; ++i){
			ans = max(ans,work(i));

			for(j = i+1 ; j <= n ; ++j){
				for(k = 1 ; k <= n ; ++k){
					a[i][k] += a[j][k];
				}

				ans = max(ans,work(i));
			}
		}

		printf("%d\n",ans);
	}

	return 0;
}


4、HDU 1231

题目与分析:

这一道题抽象一下还是,“最长连续子序列的和”,与前面的题的不同之处在于:本题须要求出最长连续子序列的首元素和尾元素。


/*
 * HDU_1231.cpp
 *
 *  Created on: 2014年6月23日
 *      Author: Administrator
 */

#include <iostream>
#include <cstdio>

using namespace std;

const int maxn = 10005;

int a[maxn];//用来记录数字序列
int ans[maxn];//ans[i]表示以a[i]结尾的最大连续子序列的和
int start[maxn];//以a[i]结尾的最长连续子序列的首元素

int n;

int main(){
	while(scanf("%d",&n),n){

		int i;
		for(i = 1 ; i <= n ; ++i){
			scanf("%d",&a[i]);
		}

		ans[1] = a[1];
		start[1] = a[1];
		for(i = 2 ; i <= n ; ++i){
			if(ans[i-1] + a[i] > a[i]){
				ans[i] = ans[i-1] + a[i];
				start[i] = start[i-1];//表明以a[i]结尾的最大连续子序列的首元素和以a[i-1]结尾的最大连续子序列的首元素是一样的
			}else{
				ans[i] = a[i];
				start[i] = a[i];//以a[i]结尾的最大连续子序列的首元素是a[i]自己
			}
		}

		int ansn = -100000;
		int pos = -1;
		for(i = 1 ; i <= n ; ++i){
			if(ans[i] > ansn){
				ansn = ans[i];
				pos = i;
			}
		}

		if(ansn >= 0){
			printf("%d %d %d\n",ansn,start[pos],a[pos]);
		}else{
			printf("%d %d %d\n",0,a[1],a[n]);
		}
	}

	return 0;
}






















版权声明:本文博客原创文章,博客,未经同意,不得转载。

你可能感兴趣的:(动态规划)