算法:DP动态规划典例

18 篇文章 0 订阅

 HDU2059

theme:龟兔赛跑,跑道长度为L,兔子速度为VR,乌龟骑一辆电动车,电动车速度为V1,充满电情况下,电动车能以V1速度骑行C米,没电时乌龟推车速度为V2,开始时为充满电状态,途中有n个充电站a[i](不在起终点),每次充电需要等t秒,乌龟可以选择充还是不充。问乌龟有没有可能赢兔子。

solution:将起点与终点也看做充电站,令dp[i]表示从起点出发到第i个充电站所需的最短时间,我们考虑走到a[i]的途中最后一个充电的地方是哪,则可推得dp[i]=min(dp[i],dp[j]+t),其中j为i之前的充电站,t为从j充满电后不再充电走到i的时间。注意特殊处理一下j=0时,因为起点时已充满电,不用加上充电的时间。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define far(i,n) for(int i=0;i<n;++i)
#define fdr(i,n) for(int i=n-1;i>=0;--i)
typedef long long ll;

int a[105];
double dp[105];

int main()
{
    int L;
    while(~scanf("%d",&L))
    {
        int n,c,t;
        scanf("%d%d%d",&n,&c,&t);
        int vr,v1,v2;
        scanf("%d%d%d",&vr,&v1,&v2);
        a[0]=0;
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        a[n+1]=L;

        far(i,n+2)
            dp[i]=2147483647;
        dp[0]=0;
        for(int i=1;i<=n+1;++i)
            for(int j=0;j<i;++j)
            {
                int l=a[i]-a[j];
                int flag=0;
                double tx;
                if(c>=l)
                    tx=1.0*l/v1;
                else
                    tx=1.0*c/v1+1.0*(l-c)/v2;
                if(j==0)
                    flag=-t;
                dp[i]=min(dp[i],dp[j]+tx+t+flag);
            }
//        far(i,n+2)
//            cout<<i<<" "<<dp[i]<<endl;
        double tr=1.0*L/vr;
        if(dp[n+1]<tr)
            printf("What a pity rabbit!\n");
        else
            printf("Good job,rabbit!\n");
    }
}
  • 0-1背包

1、饭卡 hdu2546

http://acm.hdu.edu.cn/showproblem.php?pid=2546

//√31ms 01背包的应用,要变形也变的是输出结果,不是背包模板!
/*
theme:有一张饭卡,给定原始余额为m,食堂规定只要余额>=5就可以购买任意价格的菜,及时减掉之后余额为负
,否则无法购买,及时钱够。现给定n种菜的价格,问可以使卡的余额达到最低多少?(多组案例)
input:多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。
n=0表示数据结束。
output:
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
*
*
*
*
*
*
solution:因为01背包算的是余额为i的背包能装下的最大物品(价值),所以我们可以
做减法,算出余额为i的卡最多能消费多少钱,再用开始余额减就可以得到最小余额
有一点要注意就是有个5元的限制条件,所以先利用贪心思想,从m元中拿出5元来买最贵的菜,
再用01背包算m-5的最大消费;一个菜
当然,如果m一开始就小于5则直接输出m(的d[m-5]会错!)
*/

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<cstring>
#include<queue>
using namespace std;

const int Max=1010;
int price[2000];
int dp[2000];

void zeroOneKnapsack(int maxv,int costs,int gains)
{
    for(int i=maxv;i>=costs;--i)
        dp[i]=max(dp[i],dp[i-costs]+gains);
}

int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;++i)
            scanf("%d",&price[i]);
        sort(price,price+n);
        int m;
        scanf("%d",&m);
        if(m<5){
            printf("%d\n",m);
            continue;
        }
        for(int i=0;i<n-1;++i)
            zeroOneKnapsack(m,price[i],price[i]);
        printf("%d\n",m-dp[m-5]-price[n-1]);
    }
}
/*
1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
1
4
3
0

-45
32
3
*/

此题还可计算一下所有菜价格之和,若<m则输出m-accumulate(price,price+n,0)

  • 最长非降子序列

http://acm.hdu.edu.cn/showproblem.php?pid=1257

//√31ms(1000)
/*
theme:有n个导弹依次发射过来,给出它们的发射高度,问至少要几个拦截系统可以拦截这n个导弹。
其中每个拦截系统发出的第一颗炮弹可以是任意高度,之后的每颗导弹高度不能超过上一颗。
*
*
*
*
*
solution:其实就是求最长非降子序列!令cnt[i]表示以missile[i]结尾的前i个数的最长非降子序列,
则cnt[i]等于1、每个满足missile[j]<missile[i](因为此题是不能超过,所以不能取=)的cnt[j]+1的max值。
最终的结果就是求cnt[]数组中最大元素。
*/

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;

int missile[300005];
int n;
int cnt[300005];

int dp()
{
    int ans=0;
    for(int i=0;i<n;++i)
    {
        cnt[i]=1;
        for(int j=0;j<i;++j)
            if(missile[j]<missile[i])
                cnt[i]=max(cnt[i],cnt[j]+1);
        ans=max(ans,cnt[i]);
    }
    return ans;
}

int main()
{
    while(~scanf("%d",&n))
    {
        for(int i=0;i<n;++i)
            scanf("%d",&missile[i]);
        int ans=dp();
        printf("%d\n",ans);

    }
}
/*
4 1 2 1 2
8 389 207 155 300 299 170 158 65
5 1 2 3 5 4
5 1 3 5 2 4

2
2
4
3
*/
  • 最大连续子段和

hdu1231:http://acm.hdu.edu.cn/showproblem.php?pid=1231

//OK 171ms(1000)
//注意结构体的运算符重载
//定义ans[i].sum为以a[i]结尾的最大连续子序列,所以若ans[i-1]>0,则ans[i].sum=ans[i-1].sum+a[i]
//否则ans[i].sum=a[i];最后求最大的ans[i]
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define far(i,n) for(int i=0;i<n;++i)
typedef long long ll;

ll a[10020];
int n;
struct _dp
{
    ll start;
    ll  last;//起始和结束下标
    int flag;//标记start改变了没
    ll sum;
    bool operator<(const _dp &b)
    {
        if(sum==b.sum)
            if(start==b.start)
                return last>b.last;
            else
                return start>b.start;
        return sum<b.sum;
    }
}ans[10020];

void dp(int n)
{
    ans[0].sum=a[0];
    for(int i=1;i<n;++i)
    {
        if(ans[i-1].sum>0)
        {
            ans[i].sum=ans[i-1].sum+a[i];
            ans[i].start=ans[i-1].start;
            ans[i].last=i;
        }
        else
        {
            ans[i].sum=a[i];
            ans[i].start=i;
            ans[i].last=i;
        }
    }
    /*far(i,n)
        cout<<ans[i].sum<<" "<<ans[i].start<<" "<<ans[i].last<<endl;*/
    _dp output=*max_element(ans,ans+n);
    if(output.sum<0)
        printf("0 %lld %lld\n",a[0],a[n-1]);
    else
        printf("%lld %lld %lld\n",output.sum,a[output.start],a[output.last]);

}

int main()
{
    while(scanf("%d",&n)&&n)
    {
        far(i,n)
            scanf("%lld",&a[i]);
        dp(n);
    }
}

/*
6
-2 11 -4 13 -5 -2
10
-10 1 2 3 4 -5 -23 3 7 -21
6
5 -8 3 2 5 0
1
10
3
-1 -5 -2
3
-1 0 -2
0

20 11 13
10 1 4
10 3 5
10 10 10
0 -1 -2
0 0 0
*/
  • 最大矩阵和

hdu1506http://acm.hdu.edu.cn/showproblem.php?pid=1506

//ok 109ms(1000)
/*theme:即求最大矩阵和
*
*
*
solution:考虑ans[i]为以a[i]的高为高所能形成的最大的矩阵,所以最终结果为max(ans)
即求每个a[i]左右两边分别能到达的不小于它的最大下标。
用动态规划思想(避免重复计算)减少耗时:考虑若左边比它大,则a[i-1]左边能到达的a[i]
一定能到达。右边同理,但注意:用DP求右边时需从n-1往左求
*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
#define far(i,n) for(int i=0;i<n;++i)
#define fdr(i,n) for(int i=n-1;i>=0;--i)
typedef long long ll;

ll a[100020];
int L[100020];
int R[100020];
ll ans[100020];

ll dp(int n)
{
    L[0]=0;
    R[n-1]=n-1;
    far(i,n)
    {
        L[i]=i;
        int cnt=i;
        while(cnt>0)//cnt为当前已比的位置
        {
            if(a[cnt-1]>=a[i])
                L[i]=L[cnt-1];
            else
                break;
            cnt=L[cnt-1];
        }
    }
    fdr(i,n)
    {
        R[i]=i;
        int cnt=i;
        while(cnt<n-1)
        {
            if(a[cnt+1]>=a[i])
                R[i]=R[cnt+1];
            else
                break;
            cnt=R[cnt+1];
        }
    }
    far(i,n)
        ans[i]=a[i]*(R[i]-L[i]+1);
/*far(i,n)
    cout<<ans[i]<<" ";
cout<<endl;*/
    return *max_element(ans,ans+n);
}

int main()
{
    int n;
    while(scanf("%d",&n)&&n)
    {
        far(i,n)
            scanf("%lld",&a[i]);
        printf("%lld\n",dp(n));
    }
}
/*
7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

8
4000
*/

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值