UVALive 5097 斜率DP

题意

有 N个人,挖K个洞,使得每个人能穿过去。每个人的面积为w*h。挖洞的花费为w*h。问最少花费

题解

首先需要对人进行排序,W从大到小进行排序。排序完成以后,从1到N进行扫描,移除无用元素(即H小于前面所有元素最大H的元素)。这样处理完成以后,整个序列W是单调递减的,H是单调递增的。于是列出状态转移方程,dp[i][j]=dp[i-1][a]+w[a+1]*h[j]。(其中A代表挖洞最后一个)
列出状态转移方程后可以发现,状态转移方程是二维的,因为数据量有5万,所以需要用斜率优化,将二维的状态转移方程降为一维。
其实列出来状态转移方程以后,直接用斜率DP的套路求解就可以了。化简一个W(a,b)的不等式就可以了(b为较优的方案)。不等式的化简结果为(dp[i-1][b]-dp[i-1][a])/(w[a+1]-w[b+1])<=h[i]。凡是满足这个不等式的a,b都存在b优于a,并且根据这个不等式就可以进行斜率优化,使得队列的斜率永远保持单增状态。完成上述两步操作以后,便可以将一个三维的循环转化为一个二维的循环,从而满足时间复杂度要求。

注意事项

需要注意DP的顺序,DP顺序是由状态转移方程决定的。因为dp[i][j]=dp[i-1][a]+w[a+1]*h[j],所以注定外层是i内层是j。比较坑的是内外层顺序错了也有可能过样例,并且在调试上较难调试。
另外的话,还需要注意并不一定要花费所有的挖洞机会,因此需要注意不能开滚动数组,必须要开一个二维数组,并且在程序末尾对最小值进行选择。

代码

#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 100010
#define MOD 1000000009
#define EPS 1e-10
#define int LL

using namespace std;

struct Node{
    int w,h;

    bool operator < (const Node b) const{
        if(w==b.w){
            return h>b.h;
        }
        return w>b.w;
    }
};
Node nodes[50010];
Node temp[50010];
int q[50010];
int dp[110][50010];

int getUP(int p,int i,int j){
    return dp[p][j]-dp[p][i];
}

int getDown(int i,int j){
    return nodes[i+1].w-nodes[j+1].w;
}

main(){
    int n,k;
    W(~scanf("%lld%lld",&n,&k)){
        MEM(dp,0);
        UP(i,1,n+1){
            scanf("%lld%lld",&nodes[i].w,&nodes[i].h);
        }
        sort(nodes+1,nodes+n+1);
        int pos=1;
        Node mx=nodes[1];
        temp[pos++]=nodes[1];
        UP(i,2,n+1){
            if(nodes[i].h>mx.h){
                mx=nodes[i];
                temp[pos++]=nodes[i];
            }
        }
        k=min(k,pos-1);
        memcpy(nodes,temp,sizeof(temp));
        UP(j,1,pos){
            dp[1][j]=nodes[1].w*nodes[j].h;
        }
        UP(i,2,k+1){
            int st=0,ed=0;
            q[ed++]=0;
            UP(j,1,pos){
                W(st+1<ed&&getUP(i-1,q[st],q[st+1])<=nodes[j].h*getDown(q[st],q[st+1])){
                    st++;
                }

                dp[i][j]=dp[i-1][q[st]]+nodes[q[st]+1].w*nodes[j].h;
                W(st+1<ed&&getUP(i-1,q[ed-1],j)*getDown(q[ed-2],q[ed-1])<=getUP(i-1,q[ed-2],q[ed-1])*getDown(q[ed-1],j)){
                    ed--;
                }
                q[ed++]=j;
            }
        }
        int mn=dp[1][pos-1];
        UP(i,2,k+1){
            mn=min(dp[i][pos-1],mn);
        }
        printf("%lld\n",mn);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值