土地征用 (斜率优化)

连接

题意:

有n个矩形,随意分成几组,使得花费最小,每组的花费是该组最大的宽度×最大的长度。长和宽不能交换位置。


思路:

先贪心地想一下,如果一个长和宽都很大的需要选的话,那么长度和宽度比它都要小的都可以 放到这组里面,不会有任何的额外花费。且分组是随便分,没有相邻之类的限制,那么可以先排序一下,同时可以删去那些不会有额外贡献的矩形。
然后先考虑一下 O n 2 On^2 On2的做法,很容易可以得出 d p [ i ] = m i n ( d p [ i ] , d p [ j ] + h [ i ] ∗ w [ j + 1 ] ) dp[i]=min(dp[i],dp[j]+h[i]*w[j+1]) dp[i]=min(dp[i],dp[j]+h[i]w[j+1]) ,h[i]*w[j+1]是j+i到i这段区间的总花费,我这边是优先长小的,然后宽小的,排序且删除了没有贡献的之后,长递增,宽递减。


On^2代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e4+5;

pair<ll,ll>p[N],st[N];
ll dp[N];
int top;

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld",&p[i].first,&p[i].second);
    }
    sort(p+1,p+1+n);
    p[0]={-1,-1};
    top=0;

    for(int i = 1;i <= n;i++){
        while(top && p[i].second >= st[top].second) top--;//只保留会产生贡献的
        st[++top] = p[i];
    }

    memset(dp,125,sizeof dp);
    dp[0]=0;
    for(int i=1;i<=top;i++){
        for(int j=0;j<i;j++){
            dp[i]=min(dp[i],dp[j]+st[i].first*st[j+1].second);
        }
    }
    printf("%lld\n",dp[top]);
    return 0;
}

对于数据范围大一些就需要用上斜优化了,转移方程还是dp[i]=min(dp[i],dp[j]+h[i]*w[j+1])
如果dp[i]可以从dp[j]和dp[k]两个位置转移,那么就有
d p [ i ] = d p [ j ] + h [ i ] ∗ w [ j + 1 ] dp[i]=dp[j]+h[i]*w[j+1] dp[i]=dp[j]+h[i]w[j+1] d p [ i ] = d p [ k ] + h [ i ] ∗ w [ k + 1 ] dp[i]=dp[k]+h[i]*w[k+1] dp[i]=dp[k]+h[i]w[k+1]
如果通过dp[k]转移会更优,那么就要有 d p [ j ] + h [ i ] ∗ w [ j + 1 ] &gt; d p [ k ] + h [ i ] ∗ w [ k + 1 ] dp[j]+h[i]*w[j+1]&gt;dp[k]+h[i]*w[k+1] dp[j]+h[i]w[j+1]>dp[k]+h[i]w[k+1]
移项后就是 d p [ j ] − d p [ k ] w [ k + 1 ] − w [ j + 1 ] &gt; h [ i ] \frac{dp[j]-dp[k]}{w[k+1]-w[j+1]}&gt;h[i] w[k+1]w[j+1]dp[j]dp[k]>h[i]

然后利用这个关系以及单调队列维护一下

优化代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;

pair<ll,ll>p[N],st[N];
ll dp[N];
int top;

int tb[N],fr,bk;

double cal(int j,int k){
    return 1.0*(dp[k]-dp[j])/(st[j+1].second-st[k+1].second);
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld",&p[i].first,&p[i].second);
    }
    sort(p+1,p+1+n);
    top=0;

    for(int i = 1;i <= n;i++){
        while(top && p[i].second >= st[top].second) top--;
        st[++top] = p[i];
    }

    fill(dp,dp+N,1e18);
    dp[0]=0ll;
    for(int i=1;i<=top;i++){
        for(int j=0;j<i;j++){
            dp[i]=min(dp[i],dp[j]+st[i].first*st[j+1].second);
        }
    }
    fr=bk=0;
    for(int i=1;i<=top;i++){
        while (fr<bk&&cal(tb[fr],tb[fr+1])<=st[i].first)fr++;//弹出队列前面不合法的状态
        dp[i]=dp[tb[fr]]+st[tb[fr]+1].second*st[i].first;
        while (fr<bk&&cal(tb[bk-1],tb[bk])>=cal(tb[bk-1],i))bk--;//加入当前的点需要维护单调性
        tb[++bk]=i;
    }

    printf("%lld\n",dp[top]);
    return 0;
}

//b = dp[i]
//k = l[i]
//x = r[j+1]
//y = dp[j]
//y = -k * x + b
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值