HUD1003关于分治法及DP思想的初步认识,宁品品!

本文探讨了子序列最大和问题的三种算法:枚举法、分治法和动态规划(DP)。通过实例分析,展示了不同算法的实现过程和效率对比,其中动态规划提供了最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HUD1003关于分治法及DP思想的初步认识,宁品品!


今天刚刚开始做hdu的题,做到第三道就发现不对劲了- -,这道题刚开始感觉无从下手(主要是我太菜),然后去网上搜了下,发现小东西还真有丶东西,用分治法可以做出来的题试着用DP思想做的话简直不要太简单,刷新我这个菜鸡的世界观了。

问题

题目链接 点我点我

Given a sequence a[1],a[2],a[3]…a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.

给定序列a [1],a [2],a [3] … a [n],您的工作是计算子序列的最大和。例如,给定(6,-1,5,4,-7),此序列中的最大和为6 +(-1)+ 5 + 4 = 14。

示例

输入的第一行包含一个整数T(1 <= T <= 20),它表示测试用例的数量。然后是T行,每行以数字N(1 <= N <= 100000)开头,然后是N个整数(所有整数都在-1000和1000之间)。

对于每个测试用例,您应该输出两行。第一行是“ Case#:”,#表示测试用例的编号。第二行包含三个整数,即序列中的最大和,子序列的开始位置,子序列的结束位置。如果有多个结果,请输出第一个。在两种情况之间输出空白行。

input:
2
5 6 -1 5 4 -7
7 0 6 -1 1 -6 7 -5
output:
Case 1:
14 1 4

Case 2:
7 1 6

枚举法

这种方法的思想就是每个子序列都求其和,再一一比较大小,实在是太low了,这也是我一开始首先想到的方法,害,无颜面对江东父老,代码没有难度也没有发的必要,大火搜一下枚举法就知道这个蠢办法了。

#include <stdio.h>
using namespace std
#define maxn 100000 + 2
int arr[maxn];
 
int main(){
	int t, n, maxLeft, maxRight, maxSum, id = 1;
	int thisSum;
	cin>>t;
	while(t--){
		cin>>n;
		for(int i = 0; i < n; ++i)
			cin>>arr[i];
		maxSum = arr[0];
		maxLeft = maxRight = 0;
		/*maxSubsequenceSum------O(N^3)*/
		for(int i = 0; i < n; ++i){
			for(int j = i; j < n; ++j){
				thisSum = 0;
				
				for(int k = i; k <= j; ++k){
					thisSum += arr[k];
				}
				if(thisSum > maxSum){
					maxSum = thisSum;
					maxLeft = i; maxRight = j;
				}
			}
		}
		printf("Case %d:\n%d %d %d\n", id++, maxSum, maxLeft + 1, maxRight + 1);
		if(t) printf("\n");
	}
	return 0;
}

分治法

摘自百度百科:分治法可以通俗的解释为:把一片领土分解,分解为若干块小部分,然后一块块地占领征服,被分解的可以是不同的政治派别或是其他什么,然后让他们彼此异化。

所以分治在我理解就是把一个大问题分解成许多简单的没有联系的小问题
简单在于该问题的规模缩小到一定的程度就可以容易地解决,
没有联系在于小问题之间不能存在逻辑关系,是相互独立的

问题分解与解决
此问题是求最大连续和,那么我们可以假设在其中间划分出一条界线,对两边分别求其最大连续子序列和,然后,求出分界线两边的最大连续和,

但是问题还没有结束,我们处理的只是分界线两边的连续序列,最大连续和也有可能跨分界线,这样就可能把正确结果遗漏,所以我们还要做一个新连续和,该连续和由分界线向左右两边发出,依次求出由分界线向左和由分界线向右的连续和,分界线在其中,然后两边连续和相加即可。

合并
最后判断谁是最大连续和,得出结果。
这是判断的函数

int max3(int a, int b, int c){
    if(a >= b && a >= c) return 1;
    if(b >= a && b >= c) return 2;
    if(c >= a && c >= b) return 3;
}

函数代码

int maxSubsequenceSum(int left, int right, int *l, int *r){
    int thisLeft, thisRight;
    int leftSum, rightSum, midSum, mid;
    int leftBorderSum, maxLeftBorderSum;
    int rightBorderSum, maxRightBorderSum;
    int ll, lr, rl, rr, ml, mr;
    if(left == right){
		*l = *r = left;
		return arr[left];
	}
    mid = (left + right) / 2;
    leftSum = maxSubsequenceSum(left, mid, &ll, &lr); //这里是求左边的最大和
    rightSum = maxSubsequenceSum(mid + 1, right, &rl, &rr);
    //求右边的最大和
    
    leftBorderSum = 0; thisLeft = mid;  //求中间向左的连续和
    maxLeftBorderSum = arr[mid];
    for(int i = mid; i >= left; --i){
        leftBorderSum += arr[i];
        if(leftBorderSum >= maxLeftBorderSum){
            maxLeftBorderSum = leftBorderSum;
            thisLeft = i;
        }
    }
    rightBorderSum = 0; thisRight = mid + 1;  //求中间向右的连续和
    maxRightBorderSum = arr[mid + 1];
    for(int i = mid + 1; i <= right; ++i){
        rightBorderSum += arr[i];
        if(rightBorderSum > maxRightBorderSum){
            maxRightBorderSum = rightBorderSum;
            thisRight = i;
        }
    }
    midSum = maxLeftBorderSum + maxRightBorderSum;   //跨越分界线的连续和
    
    int sign = max3(leftSum, midSum, rightSum);  //判断最大和
    
    if(sign == 1){
       maxSum = leftSum;
       *l = ll;
	   *r = lr;
    }else if(sign == 2){
        maxSum = midSum;
        *l = thisLeft;
		*r = thisRight;
    }else{
        maxSum = rightSum;
        *l = rl;
		*r = rr;
    }
    return maxSum;  //返回最大和
}

主函数大家自行发挥,就用个循环就行了

DP思想

动态规划算法基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解 。

动态规划(dynamic programming)算法是解决多阶段决策过程最优化问题的一种常用方法,难度比较大,技巧性也很强。利用动态规划算法,可以优雅而高效地解决很多贪婪算法或分治算法不能解决的问题。

上面也是百度说的
我理解的就是利用子问题之间的逻辑关系,进一步对于问题的合并进行简化,从而比分治算法的逼格深度更高了
虽然不知道我的理解对不对嗷
但是用了这种算法,这道题的过程就简洁的离谱,
这是个O(n)算法,把数组开销都省了
品,宁细品!

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int MAX_N = 100010;

int a[MAX_N];

int x, number, sum, newsum, start, endspace, cc = 0;
int main()
{
	scanf("%d", &x);
	while (x--) {
		scanf("%d", &number);
		for (int i = 0; i < number; ++i) {      //输出原始数据
			scanf("%d", &a[i]);
		}
		sum = -2000, start = newsum = 0;    //初始化三要素
		int pre = 0;
		for (int i = 0; i < number; ++i) {  
			newsum += a[i];
			if (newsum > sum) {       //判断newsum对于sum的作用
				sum = newsum;
				start = pre, endspace = i;
			}
			if (newsum < 0) {    
				newsum = 0;
				pre = i + 1;
			}
		}
		if (cc) printf("\n");    //题目输出的格式要求
		printf("Case %d:\n", ++cc);
		printf("%d %d %d\n", sum, start + 1, endspace + 1);
	}
	return 0;
}

在这个问题中主要是利用了newsum > sum
即newsum+a[i]<a[i] 的比较,
也就是说判断前一个数的最大和对于现在这个数的最大和有积极作用(使之变大)还是消极作用(使之减小),利用这一点决定是否将其算入最大和中,而现在这个数的位置就是现在所算最大和的终点,从而在一遍循环中无形的既完成问题的分解,又完成小问题结果的合并,让人不禁赞叹:“妙啊!~~~~~”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值