动态规划(dp)入门——杭电OJ1003

 

本题用暴力解法时间复杂度高达O(n^3),会超时,因此找一种合适的方法来简化问题;

    最优子序列是个序列,那么必然含有起始位置和终止位置,我们以每个a[i]作为研究对象,研究以i为终止位置的最优子序列,得出唯一确定的以a[i]为结尾的最优子序列,每个a[i]有一个以他为结尾的最优子序列,再便利所有的最优子序列,找出最优子序列里的最优为最终结果;

    本题对最优的定义是和数最大(Max Sum);定义数组a[]用来接收一组测试用例的所有数字;我们定义结构体数组dp[],dp[i]用来记录以a[i]为结尾的最大子序列的信息,dp[i].x用来记录最大子序列起始位置,dp[i].val用来记录最大子序列的和。

    易知:dp[0].val=a[0],dp[0].x=0(以a[0]为结尾的序列只有一个,即a[0]本身,因此dp[0]记录的是序列(a[0])的和and位置);

    通过dp[0]推导dp[1]、dp[2]、dp[3]、、、、、、dp[n-1];

    推导思路:重点:dp[0]是以a[0]为结尾的最大子序列的一个记录,dp[1]必然含有a[1](因为dp[i]本身的含义是以a[i]为结尾的最大子序列,不含a[1]就不是以a[i]为结尾的序列了),dp[2]必然含有a[2]、、、、、、

    首先,推dp[1],已知dp[0]是以a[0]为结尾的最大子序列,将dp[0]链接a[1],求dp[1],这个如何思考呢;

1    前面的序列和>0,a[1]>0,此时的情况记作++,此时序列无疑是越加越大的( dp[0]+a[1]>a[1] ),以a[1]为结尾的序列(dp[1])必然是前面的最大序列加上a[1];

2    前面的序列和>0,a[1]<0,此时为+-,此时序列无疑是越加越小的,但是仍然必须加上a[1],因为dp[1]规定了必须以a[1]为结尾,此时对于dp[1],因为 dp[0]+a[1]>a[1] ,所以dp[1]不取a[1]取dp[0]+a[1];

3    -+,此时dp[0]<0,既然我们要找一个以a[1]为结尾的最大子序列,a[1]本身就比a[1]+dp[0]大( dp[0]+a[1]<a[1] ),那么这个序列(dp[1])只取a[1]就够了,不需要加上dp[0](加上前面的序列会越加越小,不是最大取法);

4    --,既然都是负数,那必然越加越小,即( dp[0]+a[1]<a[1] ),同上,取a[1]为dp[1]唯一值(此时可能有人会懵:俩都是负的,万一dp[0]>a[1]呢,那按最大的取不应该取dp[0]而抛弃a[1]吗,这就回到了重点:dp[1]必然含有a[1](因为dp[i]本身的含义是以a[i]为结尾的最大子序列),所以dp[1]只能取a[1]抛弃前面的dp[0]);

    从以上总结出规律:dp[0]+a[1]>a[1]时,取dp[0]+a[1]为dp[1];dp[0]+a[1]<=a[1]时,取a[1]为dp[1];再从dp[0]、a[1]、dp[1]推广到dp[i-1]、a[i]、dp[i],得到状态转移方程:dp[i]=max(dp[i-1]+a[i],a[i]);
本题的具体思路都来源这篇博客 ,只不过将代码改成了C++的模式

#include<iostream>
using namespace std;
const int MAXN = 100000;
struct dp
{
	int x,val;//x代表起始位置  而val则代表的是前i个数的最优的和的数值 
};
int main()
{
	int num,i,j,q = 1;
	cin>>num;
	while(num--)
	{
		int n,a[MAXN];
		cin>>n;
		for(i = 0;i<n;i++)
		cin>>a[i];
		dp dp[MAXN];
		//挨个输入之后
		dp[0].x = 0;//起始位置为0
		dp[0].val = a[0];
		
		for(i =1;i<n;i++)
		if(dp[i -1].val+a[i]>=a[i])//
		{
			dp[i].x = dp[i-1].x;
			dp[i].val = dp[i -1].val+a[i];
		 } 
		 else
		 {
		 	dp[i].x = i;//这个起始位置就是这个本身
			dp[i].val = a[i];	
		 }//最优解目前已经找到 
		 int c,b,max = -1001;//这里设置最小的值是-1001 
		 for(i = 0;i<n;i++)
		 {
		 	if(max<dp[i].val)
		 	{
		 		max = dp[i].val;
		 		c = dp[i].x;
		 		b = i;
			 }
		 }
		 cout<<"Case "<<q++<<":"<<endl;
		 cout<<max<<" "<<c+1<<" "<<b+1<<endl;
		 if(num!=0) cout<<endl;
		
	 } 
	 return 0; 
}

其实这一题也可以不用动态规划,因为这里的每一个子序列都必须使用最后一个值,并那最后一个值和之前的值进行比较,从而可以得出一个最大的值,时间复杂度都是一样的,O(N)。

#include<iostream>
using namespace std;
 
int main(){
	int n,m;
	cin>>n;
	int t=1;
	while(t<=n){
	cin>>m;
		int a;
		int sum=0,begin=0,end=0;
        int tempbegin=1;//临时子序列首元素
		int max_sum=-1001; //这里最大和的赋初值为-1001 的作用就是能够确保将输入的第一个任何大小的数(在[-1000,1000]之间的数)都能够执行下叙的第一个if语句。
		for(int i=1;i<=m;i++){
			cin>>a;
			sum+=a;
			if(sum>max_sum){
				max_sum=sum;
				end=i;
				begin=tempbegin;
			}
			if(sum<0){
				tempbegin=i+1;
				sum=0;
			}
		}
		cout<<"Case "<<t<<":"<<endl<<max_sum<<" "<<begin<<" "<<end<<endl;
		if(t<n)cout<<endl;
		t++;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值