UVALive - 5097 Cross the Wall 斜率DP

Cross the Wall UVALive - 5097
N N N 个人,每个人有自己的宽高 w i , h i w_i,h_i wi,hi ,最多可以挖 K K K 个洞,并且不能重叠。挖一个宽高分别为 w , h w,h w,h 的洞需要花费 w × h w\times h w×h 元。当人的宽高 w i ≤ w , h i ≤ h w_i\le w,h_i\le h wiw,hih 时,这个人才能通过这个洞。问至少要花费多少钱才能使所有人都从洞口通过。

首先按照宽高从大到小进行排序,首先按宽,宽相等时再按高来排序。假如存在 w 1 ≤ w 2 w_1\le w_2 w1w2 并且 h 1 ≤ h 2 h_1\le h_2 h1h2 ,那么就去掉 ( w 1 , h 1 ) (w_1,h_1) (w1,h1) 这个点,因为后一个点能过去的话,这个点也一定能过去。这样排序去重之后,对于宽是单调递减的,而对于高是单调递增的(想一想,为什么),因为假如后面的高小于前面的话,由于宽递减,那么这个点会被剔除;假如宽相等,那么后面的高小于等于前面的高,因此这个点也会被剔除。

状态转移方程为:

d p [ i ] [ j ] = min ⁡ { d p [ k ] [ j − 1 ] + w [ k + 1 ] × h [ i ] } dp[i][j]=\min\{dp[k][j-1]+w[k+1]\times h[i]\} dp[i][j]=min{dp[k][j1]+w[k+1]×h[i]}

其中 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个人从 j j j 个洞钻过去的最小花费,中间枚举 k k k ,即前 k k k 个人钻 j − 1 j-1 j1 个洞,第 k + 1 k+1 k+1 到第 i i i 个人钻一个洞,能够让这部分所有人都钻过去的洞的大小是 w [ k + 1 ] × h [ i ] w[k+1]\times h[i] w[k+1]×h[i](因为高是递增的,所以第 i i i 个高为最大的高)。

k 1 < k 2 k_1< k_2 k1<k2 ,若 k 2 k_2 k2 优于 k 1 k_1 k1 ,则:

d p [ k 2 ] [ j − 1 ] + w [ k 2 + 1 ] h [ i ] < d p [ k 1 ] [ j − 1 ] + w [ k 1 + 1 ] h [ i ] d p [ k 2 ] [ j − 1 ] − d p [ k 1 ] [ j − 1 ] w [ k 1 + 1 ] − w [ k 2 + 1 ] < h [ i ] \begin{aligned} dp[k_2][j-1]+w[k_2+1]h[i]&<dp[k_1][j-1]+w[k_1+1]h[i]\\ \dfrac{dp[k_2][j-1]-dp[k_1][j-1]}{w[k_1+1]-w[k_2+1]}&<h[i] \end{aligned} dp[k2][j1]+w[k2+1]h[i]w[k1+1]w[k2+1]dp[k2][j1]dp[k1][j1]<dp[k1][j1]+w[k1+1]h[i]<h[i]

这样就得到了常规的斜率优化的式子。
代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
//#define WINE
#define MAXN 50010
using namespace std;
typedef long long ll;
int n,k,cnt,h,t,q[MAXN];
ll dp[MAXN][105];
struct Node{
    int w,h;
}a[MAXN];
bool cmp(Node a,Node b){
    if(a.w>b.w)return true;
    if(a.w==b.w&&a.h>b.h)return true;
    return false;
}
ll up(int k2,int k1,int j){
    return dp[k2][j-1]-dp[k1][j-1];
}
ll down(int k2,int k1){
    return a[k1+1].w-a[k2+1].w;
}
ll getDP(int i,int k,int j){
    return dp[k][j-1]+1ll*a[k+1].w*a[i].h;
}
int main(){
#ifdef WINE
    freopen("data.in","r",stdin);
#endif
    while(scanf("%d%d",&n,&k)!=EOF){
        for(int i=1;i<=n;i++)
            scanf("%d%d",&a[i].w,&a[i].h);
        sort(a+1,a+n+1,cmp);
        cnt=1;
        for(int i=2;i<=n;i++)
            if(a[i].w<a[cnt].w&&a[i].h>a[cnt].h)
                a[++cnt]=a[i];
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=cnt;i++)dp[i][1]=a[1].w*a[i].h;
        k=min(k,cnt);
        for(int j=2;j<=k;j++){
            h=t=0;q[t++]=0;
            for(int i=1;i<=cnt;i++){
                while(h+1<t&&up(q[h+1],q[h],j)<=a[i].h*down(q[h+1],q[h]))
                    h++;
                dp[i][j]=getDP(i,q[h],j);
                while(h+1<t&&up(i,q[t-1],j)*down(q[t-1],q[t-2])<=up(q[t-1],q[t-2],j)*down(i,q[t-1]))
                    t--;
                q[t++]=i;
            }
        }
        printf("%lld\n",dp[cnt][k]);
    }
    return 0;
}

在网站上提交的时候是WA,此时找了好几个其他博客的代码,发现也是WA,然后去 uDebug 上造数据如下:

5 6
100 8
300 7
500 6
600 19
700 30

输出为 5600 ,但是显然是错的,排序去重之后只会剩下 700,30 这个点,所以最小花费应该是 21000 ,或许测评那边出问题了吧…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值