斜率优化 笔记

4 篇文章 0 订阅
1 篇文章 0 订阅

梗概

目前只会斜率优化的naive的版本,动态维护凸包的版本目前不会,以后会的时候再更新

我们在做dp问题时经常性的会发现时间复杂度完全不够,那么我们需要用到优化了
dp的优化有很多,这里不再赘述,单说斜率优化。

总条件

f[i]=max(f[k]+g[i]*g[k]+b)(有决策点k的状态,有i和k的状态,有常数b)
然后将常数项和当前要求的f[i]算作B
B=f[i]+b B = f [ i ] + b
将只与f[k]有关的算作y
y=f[k]+... y = f [ k ] + . . .
将与g[k]和g[i]有关的 g[k]算作x g[i]算作k
B=y+kx==>y=Bkx B = y + k ∗ x ==> y = B − k ∗ x
然后就是类似于数学中的线性规划问题,用一条斜率是定值的直线求和之前状态构成的凸包的交点,交点就是当前的决策点。


情况一:若决策的横坐标 x[j] x [ j ] 和斜率 k k 单调,我们只需维护一个单调队列
每次类似GrahamScan算法一样加点、删点
询问时根据斜率单调移动询问指针
时间复杂度为 O(n) O ( n )

情况二:若决策的横坐标 x[j] x [ j ] 和斜率 k k 不单调,我们则需要一个高级数据结构来动态维护凸包,显然平衡树能够胜任
加点时我们需要二分横坐标位置,并左右删点以维护凸壳凸性
在询问时则需二分凸壳的斜率以得到直线与凸壳的切点
时间复杂度为O(nlogn)
引用刘明华dalao的课件内容。


[HNOI2008]玩具装箱toy
一道较为好想的dp题
暴力异常好想:
f[j]=min(f[k]+(jk1+sum[j]sum[k]L)2) f [ j ] = m i n ( f [ k ] + ( j − k − 1 + s u m [ j ] − s u m [ k ] − L ) 2 )
下面稍微整一下
g[i]=sum[i]+i;L=L+1; g [ i ] = s u m [ i ] + i ; L = L + 1 ; 方便讨论
(g[j]g[k]L)2=g[j]g[j]2g[j]L+LL+g[k]g[k]2g[k]g[j]+2Lg[k]; ( g [ j ] − g [ k ] − L ) 2 = g [ j ] ∗ g [ j ] − 2 ∗ g [ j ] ∗ L + L ∗ L + g [ k ] ∗ g [ k ] − 2 ∗ g [ k ] ∗ g [ j ] + 2 ∗ L ∗ g [ k ] ;
移一下项
f[j]+2g[j]LLLg[j]g[j]=f[k]+2Lg[k]+g[k]g[k]2g[k]g[j] f [ j ] + 2 ∗ g [ j ] ∗ L − L ∗ L − g [ j ] ∗ g [ j ] = f [ k ] + 2 ∗ L ∗ g [ k ] + g [ k ] ∗ g [ k ] − 2 ∗ g [ k ] ∗ g [ j ]
B=f[j]+2g[j]LLLg[j]g[j]; B = f [ j ] + 2 ∗ g [ j ] ∗ L − L ∗ L − g [ j ] ∗ g [ j ] ;
y=f[k]+g[k]g[k]+2Lg[k]; y = f [ k ] + g [ k ] ∗ g [ k ] + 2 ∗ L ∗ g [ k ] ;
k=2g[j]; k = − 2 ∗ g [ j ] ;
x=g[k]; x = g [ k ] ;
B=y+kx; B = y + k x ;
y=kx+B; y = − k x + B ;
这样就可以dp优化了,用单调队列维护

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=50000+10;
ll f[maxn],g[maxn],sum[maxn],a[maxn];
int n;
ll L;
struct node
{
    ll x,y; int id;
}q[maxn];
double getk(int x,int y)
{
    return (q[y].y-q[x].y)*1.0/(q[y].x-q[x].x);
}
int main()
{
    freopen("bzoj_1010.in","r",stdin);
    freopen("bzoj_1010.out","w",stdout);
    scanf("%d %lld",&n,&L); L++;
    for(int i=1; i<=n; i++)
    {
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i]; g[i]=sum[i]+i;
    }
    int head=0,tail=0;
    for(int i=1; i<=n; i++)
    {
        while(head<tail && getk(head,head+1)<=2*g[i]) head++;
        f[i]=-2*g[i]*q[head].x+q[head].y-2*g[i]*L+L*L+g[i]*g[i];
        ll y=f[i]+g[i]*g[i]+2*L*g[i],x=g[i];
        while(tail>head && ((y-q[tail-1].y)*1.0/(x-q[tail-1].x))<getk(tail,tail-1)) tail--;
        tail++; q[tail].y=y; q[tail].x=x; q[tail].id=i;
    //  cout<<f[i]<<endl;
    }
    cout<<f[n]<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值