【C++心路历程24】龙珠【dp加单调队列】

【问题描述】

  你得到了一个龙珠雷达,它会告诉你龙珠出现的时间和地点。

  龙珠雷达的画面是一条水平的数轴,每一个窗口时间,数轴的某些点上会出现同一种龙珠,每当你获得其中一颗龙珠,其它龙珠就会消失。下一个窗口时间,数轴上又会出现另一种龙珠。总共有n个窗口时间,也就是总共有n种龙珠。

  假设你会瞬间移动,你从数轴的x点移动到y点,耗时0秒,但是需要耗费|x-y|的体力。同时,挖出一颗龙珠也需要耗费一定的体力。请问,最少耗费多少体力,就可以收集齐所有种类的龙珠。

【输入格式】

  第一行,三个整数n,m,x,表示共有n个窗口时间,每个窗口时间会出现m个龙珠,x是一开始你所处的位置。
  接下来有两个n*m的矩阵。
  对于第一个矩阵,坐标为(i,j)的数字表示第i个窗口时间,第j个龙珠的位置。
  对于第二个矩阵,坐标为(i,j)的数字表示第i个窗口时间,挖取第j个龙珠所需的体力。

【输出格式】

  一个整数,表示所需最小体力

【输入样例】

3 2 5
2 3
4 1
1 3
1 1
1 3
4 2

【输出样例】

8

【数据范围】

所有数据均为整数
数轴范围在0到30000
挖一颗龙珠所需体力不超过30000
结果保证在int范围
对于50%的数据,1<=n<=50,1<=m<=500。
对于100%的数据,1<=n<=50,1<=m<=5000。

不难分析出动态规划方程:
设结构体:a[i][j].x=表示第 i 个时间窗口出现的第 j 个龙珠的位置。
:a[i][j].c=表示第 i 个时间窗口挖第 j 个龙珠需要耗费的体力。
//f(i,j)表示在第i个窗口拾取第j个龙珠后所耗费的最小体力
//f(i,j)=min(f(i-1,k)+abs(a[i-1][k].wz-a[i][j].wz) || 1<=k<=m )+a[i][j].c
边界:
f(1,j)= |a[1][j].x-x0|+ a[1][j].c //在第 1 个时间窗口收集第 j 个龙珠需要的体力

纯粹dp:

solve50()
    for(int j=1;j<=m;j++)
        f[1][j]=abs(a[1][j].wz-p)+a[1][j].c;
    //f(i,j)表示在第i个窗口拾取第j个龙珠后所耗费的最小体力
    //f(i,j)=min( f(i-1,k)+abs(p[i-1][k].wz-p[i][j].wz)  || 1<=k<=m )+p[i][j].c 
    for(int i=2;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            ans=inf;
            for(int k=1;k<=m;k++)
            {   
                rr=f[i-1][k]+abs(a[i-1][k].wz-a[i][j].wz);
                ans=min(ans,rr);
            }
            f[i][j]=ans+a[i][j].c;
        }   
    }
    ans=inf;
    for(int i=1;i<=m;i++)
        ans=min(ans,f[n][i]);
    printf("%d",ans);   

加入单调队列优化(偷个懒,因为这个滑动窗口左端固定,不用记录下标,只找最小值):

solve100()
for(int i=2;i<=n;i++)
    {
        int k=1;
        int minv=inf;
        //when(p[i][j].wz>=p[i-1][k].wz) 
    //f(i,j)=min(f(i-1,k)-p[i-1][k].wz)  || 1<=k<=m )+p[i][j].c+p[i][j].wz 
        for(int j=1;j<=m;j++)
        {
            while(k<=m && a[i][j].wz>=a[i-1][k].wz)
            {
                t=f[i-1][k]-a[i-1][k].wz;
                minv=min(minv,t);
                k++;
            }
            f[i][j]=minv+a[i][j].c+a[i][j].wz; 
        }   
        k=m,minv=inf;
            //when(p[i][j].wz<p[i-1][k].wz) 
    //f(i,j)=min(f(i-1,k)+p[i-1][k].wz)  || 1<=k<=m )+p[i][j].c-p[i][j].wz  
        for(int j=m;j>=1;j--)
        {
            while(k>=1 && a[i][j].wz<a[i-1][k].wz)
            {
                t=f[i-1][k]+a[i-1][k].wz;
                minv=min(minv,t);
                k--;
            }
            f[i][j]=min(f[i][j],minv+a[i][j].c-a[i][j].wz); 
        }   
    }
    ans=inf;
    for(int i=1;i<=m;i++)
        ans=min(ans,f[n][i]);
    printf("%d",ans);   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值