20200329DP训练总结

动态规划实际上是将总过程分成若干个互相联系的阶段,在它的每一阶段都需要做出决策,从而使整个过程达到最好的活动效果。各个阶段决策的选举不是任意确定的,它依赖于当前的状态,又影响以后的发展。
无论过去的状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略,即构建最优子结构,此为动态规划的最优化原理。其次还要注意动态规划的无后效性原理,即某阶段的状态一旦确定,则此后过程的演变不再受此前各状态及决策的影响。
经典例题:完全背包问题
1.Milking Times
Bessie is such a hard-working cow. In fact, she is so focused on maximizing her productivity that she decides to schedule her next N (1 ≤ N ≤ 1,000,000) hours (conveniently labeled 0…N-1) so that she produces as much milk as possible.

Farmer John has a list of M (1 ≤ M ≤ 1,000) possibly overlapping intervals in which he is available for milking. Each interval i has a starting hour (0 ≤ starting_houri ≤ N), an ending hour (starting_houri < ending_houri ≤ N), and a corresponding efficiency (1 ≤ efficiencyi ≤ 1,000,000) which indicates how many gallons of milk that he can get out of Bessie in that interval. Farmer John starts and stops milking at the beginning of the starting hour and ending hour, respectively. When being milked, Bessie must be milked through an entire interval.

Even Bessie has her limitations, though. After being milked during any interval, she must rest R (1 ≤ R ≤ N) hours before she can start milking again. Given Farmer Johns list of intervals, determine the maximum amount of milk that Bessie can produce in the N hours.

Input

  • Line 1: Three space-separated integers: N, M, and R
  • Lines 2…M+1: Line i+1 describes FJ’s ith milking interval withthree space-separated integers: starting_houri , ending_houri , and efficiencyi

Output

  • Line 1: The maximum number of gallons of milk that Bessie can product in the N hours

Sample Input
12 4 2
1 2 8
10 12 19
3 6 24
7 10 31
Sample Output
43
该题大意为给某头特定的奶牛产奶都能获得特定的效益,且为两头奶牛产奶中间必须有一段时间的休息时间,问此人获得的最大效益是多少。首先对各个时间段进行排序,dp【i】代表假设为第i头牛产奶,继而通过循环和不断比较来确定dp【i】的值,从而可以计算出产生的价值总和的最大值。
最终结果为dp【i】中的最大值。
AC代码

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long ll;
struct cow{
    ll Begin,End,eff;
}a[1005];
ll dp[1005];
bool cmp(cow p,cow q)
{
    if(p.End!=q.End)
        return p.End<q.End;
    else return p.Begin<q.Begin;
}
ll n,m,r,i,j;
int main()
{
    scanf("%d%d%d",&n,&m,&r);
    for(i=1;i<=m;i++)
        scanf("%d%d%d",&a[i].Begin,&a[i].End,&a[i].eff);
    sort(a+1,a+1+m,cmp);
    dp[0]=0;
    for(i=1;i<=m;i++)
    {
        dp[i]=a[i].eff;
        for(j=i-1;j>0;j--)
            if(a[i].Begin-a[j].End>=r)
                dp[i]=max(dp[i],dp[j]+a[i].eff);
    }
    ll Max=-1;
    for(i=1;i<=m;i++)
        if(dp[i]>Max) Max=dp[i];
    printf("%d",Max);
    return 0;
}

2.最大M子段和问题
题目简介
描述:给定 N (1 <= N <= 1000000) 个绝对值不超过 32768 的整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[N],求从该序列中取出 M 个连续不相交子段,使这 M 个子段的和最大。如果子段全都是负数则最大子段和也为负数。
输入:有一个正整数 M 和 一个正整数 N,后面紧跟 N 个绝对值不大于 32768 的整数
输出:最大 M 子段和
样例输入:
2 6 -1 4 -2 3 -2 3
样例输出:
8
令 dp[i][j] 表示在前i个数中选取j段且第i个数在最后一组中的最大子段和,则可分析状态转移方向为

dp[i][j]=max(dp[i-1][j],max{dp[k][j-1]|j - 1<= k < i})+a[i];

来源于对第i个数的两种决策
①第i个数和第i-1个数连接成一段
dp[i][j] = dp[i-1][j] + a[i]
②第i个数自己单独做一段.那么前面就需要有 j-1段
dp[i][j] = max{dp[k][j-1]|j-1<=k<i}+a[i]
同时为了减少时间和空间复杂度,可以使用滚动数组
解决空间问题

for(int j=1;j<=m;j++)
       {
           j1=1;
   	for(int i=j;i<=n; i++)
           {
               //dp[i][j0]存的是dp[i][j-1]的值
   	   //dp[i][j1] 存的是dp[i][j]的值
               dp[i][j1]=i-1<j?-INF:dp[i-1][j1]; 
               for(int k=j-1;k<i;++k)
                   dp[i][j1]=max(dp[i][j1],dp[k][j1^1]);
               dp[i][j1] += a[i];
               if(j == m) ans = max(ans, dp[i][j1]);
           }
           j1^=1; //滚动数组交换0,1。
       } 
       printf("%d\n", ans);
   }
   return 0;
}       

解决时间问题

for(int j=1;j<=m;j++)
	{
            for(int i=j;i<=n;i++)
            {
               dp[i][j1]=i-1<j?-INF:dp[i-1][j1]; 
                for(int k=j-1;k<i;++k)
                    dp[i][j1]=max(dp[i][j1],dp[k][j0]);
 		//这轮循环只比上轮多了个dp[i-1][j0]!!!
                dp[i][j1] += a[i];
                if(j == m) ans = max(ans, dp[i][j1]);
            }
            swap(j0, j1); //
        }
	这轮只比上轮多了一个数,上轮的值ma记录下来,这轮只用和 dp[i-1][j0] 比较一下就行了。

整体代码

 #include <stdio.h>
int dp[1000005],num[1000005],n,m;
int max(int a,int b)
{
	return a>b ? a:b;
}
int solve()
{       memset(dp,0,sizeof(dp));
	for(int i=1;i<=m;i++)
	{
		int step=0;
		for(int k=1;k<=i;k++)
			step+=num[k];
		dp[n]=step;
		for(int j=i+1;j<=n;j++)
		{
			step=max(step,dp[j-1])+num[j];
			dp[j-1]=dp[n];//维护分i段时,前j-1个数构成的最大和。注意d[j]以后的数据还是分i-1段时的情况。这是关键
			dp[n]=max(step,dp[n]);//保存了分i段时,前j个数构成的最大和。 
		}
	}
	return dp[n];
}
int main()
{
	while(scanf("%d%d",&m,&n)!=EOF)
	{
		for(int i=1;i<=n;i++)
		{
			dp[i]=0;
			scanf("%d",&num[i]);
		}
		printf("%d\n",solve());
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值