CSU 1963 斜率DP

题意

有N个点,M只兔子,P个饲养员。每只兔子都有一个出现时间,只有在出现时间之后喂食才是有效的。 一个饲养员从任意时间点从起点出发,每分钟走一步,不能停留。要求喂完所有的兔子,问兔子的最小等待时间总和。

题解

这道题非常适合用数形结合的思想去解决,CSU-1963 Feed the rabbit(斜率优化dp)这篇博文讲的非常好,非常新颖而且易懂。
我这里也讲一下自己的理解吧,因为有两个量,时间和距离,首先可以将这个两个量映射到二维空间。很容易便能发现,饲养员所走的路程是一条斜率为1的直线,如果兔子想要被喂食,那么必须要在这条直线的下方出现。这样的话,为了方便处理,我们可以将兔子的点映射到Y轴上。这样的话,一个二维的问题就通过数形结合成功转化为一个一维问题。
我们只需要分配P个点,使得每个兔子点到其上方最近饲养员点的距离最小就可以了。很明显的一个斜率DP的套路。dp[i][j]=dp[i-1][k]+(j-k)*y[j]-(sum[j]-sum[k])。i代表分配了i个饲养员,j代表最后一个饲养员点在j,sum代表前缀和。最后斜率优化一下就可以了。

注意事项

需要注意的是一定要从dp[0][0]开始,其他的dp值都初始化成INF,这样才能保证为了喂了的兔子。

代码

#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 100050
#define MOD 1000000007
#define EPS 1e-3
#define int LL
using namespace std;

int dp[110][MAXN];
int x[MAXN],y[MAXN],sum[MAXN],q[MAXN];

int getUp(int a,int b,int i) {
    return dp[i-1][b]-dp[i-1][a]+sum[b]-sum[a];
}

int getDown(int a,int b) {
    return b-a;
}

int getDp(int i,int j,int k)
 {
    return dp[i-1][k]+(j-k)*y[j]-(sum[j]-sum[k]);
}

main() {
//    freopen("d://feed5.in","r",stdin);
    int n,m,p;
    W(~scanf("%lld%lld%lld",&n,&m,&p)) {
        MEM(x,0);
        MEM(y,0);
        MEM(dp,0);
        MEM(sum,0);
        UP(i,1,n) {
            int xx;
            scanf("%lld",&xx);
            x[i+1]=x[i]+xx;
        }
        m++;
        UP(i,1,m) {
            int a,b;
            scanf("%lld%lld",&a,&b);
            y[i]=(b-x[a]);
        }
        sort(y+1,y+m);
        UP(i,2,m) {
            y[i]-=y[1];
            sum[i]=y[i]+sum[i-1];
        }
        MEM(dp,INF);
        dp[0][0]=0;
        y[1]=0;
        UP(i,1,p+1) {
            int st=0,ed=0;
            q[ed++]=0;
            UP(j,1,m) {
//                cout<<getUp(q[st],q[st+1],i)<<endl;
                W(st+1<ed&&getUp(q[st],q[st+1],i)<=y[j]*getDown(q[st],q[st+1])) {
                    st++;
                }
                dp[i][j]=getDp(i,j,q[st]);
//            cout<<dp[i][j]<<" "<<i<<" "<<j<<" "<<q[st]<<endl;
                W(st+1<ed&&getUp(q[ed-1],j,i)*getDown(q[ed-2],q[ed-1])<=getDown(q[ed-1],j)*getUp(q[ed-2],q[ed-1],i)) {
                    ed--;
                }
                q[ed++]=j;
            }
        }
        printf("%lld\n",dp[p][m-1]);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值