#斜率优化,单调队列,动态规划#bzoj 2684 洛谷 4360 锯木场选址

题目链接

分析

d p [ i ] dp[i] dp[i]表示第二个锯木场修到第i个位置
那么状态转移方程是 d p [ i ] = m i n ( t o t − d i s [ j ] ( 从 j 到 n 的 距 离 ) ( 后 缀 和 ) ∗ ( ∑ k = 1 j w [ k ] ( 前 缀 和 ) ) − d i s [ i ] ∗ ( ∑ k = j + 1 i w [ k ] ) ) ( j &lt; i ) dp[i]=min(tot-dis[j](从j到n的距离)(后缀和)*(\sum_{k=1}^{j}w[k](前缀和))-dis[i]*(\sum_{k=j+1}^{i}w[k]))(j&lt;i) dp[i]=min(totdis[j](jn)()(k=1jw[k]())dis[i](k=j+1iw[k]))(j<i)
然而这样肯定会超时,所以必须维护一个最小的 j j j,使 d p dp dp方程最优,所以说就用到了斜率优化
d p dp dp方程剖析,就得到
t o t − d i s [ j ] ∗ s u m [ j ] − d i s [ i ] ∗ ( s u m [ i ] − s u m [ j ] ) tot-dis[j]*sum[j]-dis[i]*(sum[i]-sum[j]) totdis[j]sum[j]dis[i](sum[i]sum[j])
把已知项移出来得到
t o t − d i s [ i ] ∗ s u m [ i ] − d i s [ i ] ∗ s u m [ j ] − d i s [ j ] ∗ s u m [ j ] tot-dis[i]*sum[i]-dis[i]*sum[j]-dis[j]*sum[j] totdis[i]sum[i]dis[i]sum[j]dis[j]sum[j]
若使 k ( j &lt; k ) k(j&lt;k) k(j<k)更优秀,那么
t o t − d i s [ i ] ∗ s u m [ i ] − d i s [ i ] ∗ s u m [ j ] − d i s [ j ] ∗ s u m [ j ] &gt; t o t − d i s [ i ] ∗ s u m [ i ] − d i s [ i ] ∗ s u m [ k ] − d i s [ j ] ∗ s u m [ k ] tot-dis[i]*sum[i]-dis[i]*sum[j]-dis[j]*sum[j]&gt;tot-dis[i]*sum[i]-dis[i]*sum[k]-dis[j]*sum[k] totdis[i]sum[i]dis[i]sum[j]dis[j]sum[j]>totdis[i]sum[i]dis[i]sum[k]dis[j]sum[k]
最后得到 d i s [ k ] ∗ s u m [ k ] − d i s [ j ] ∗ s u m [ j ] s u m [ k ] − s u m [ j ] &lt; d i s [ i ] \frac{dis[k]*sum[k]-dis[j]*sum[j]}{sum[k]-sum[j]}&lt;dis[i] sum[k]sum[j]dis[k]sum[k]dis[j]sum[j]<dis[i]
可以发现, d i s dis dis是单调递减的,也就是维护上凸壳,用单调队列实现


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
int d[20001],w[20001],sum,n,head=1,tail=1,q[20001],ans=2147483647;
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+c-48,c=getchar();
    return ans;
}
inline signed min(int a,int b){return (a<b)?a:b;}
inline double slope(int x,int y){return 1.0*(d[x]*w[x]-d[y]*w[y])/(w[x]-w[y]);}
inline signed count(int x,int y){return sum-d[y]*w[y]-d[x]*(w[x]-w[y]);}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i) w[i]=iut(),d[i]=iut();
    for (rr int i=n-1;i;--i) d[i]+=d[i+1];
    for (rr int i=1;i<=n;++i) sum+=d[i]*w[i],w[i]+=w[i-1];
    for (rr int i=1;i<=n;++i){
        while (head<tail&&slope(q[head],q[head+1])>d[i]) ++head;//找出最优的队头
        ans=min(ans,count(i,q[head]));
        while (head<tail&&slope(q[tail-1],q[tail])<slope(q[tail],i)) --tail;//移去不优的队尾
        q[++tail]=i;
    }
    return !printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值