[BZOJ1010][HNOI2008]玩具装箱[BZOJ1911][APIO2010]特别行动队[BZOJ1492][NOI2007]货币兑换 斜率优化

今天因为刷不动ydc的题,只能搞搞LCT和斜率优化,上午搞了LCT,下午搞斜率优化,其实我本来是想看冬令营的课件的,发现正好有斜率优化,也写得很好。

玩具装箱

直接上方程好了。
dp[i] 表示前 i 个玩具的答案,记s[i]为物品前缀和,那么:
dp[i]=min(dp[j]+(ij+s[i]s[j]L)2),(j<i) 这个很显然吧。
定义 g[i]=i+s[i]L,f[i]=i+s[i]
我们将式子划开:
dp[i]=min(dp[j]+(g[i]f[j])2)=min(dp[j]+g2[i]2g[i]×f[j]+f2[j])=min(dp[j]+f2[j]2g[i]×f[j])+g2[i]
现在我们考虑,对于一个 j1<j2 j1 j2 优的条件是什么。
显然:
dp[j1]+f2[j1]2g[i]×f[j1]<dp[j2]+f2[j2]2g[i]×f[j2]
y[i]=dp[i]+f2[i]
y[j1]y[j2]<2g[i](f[j1]f[j2])
y[j1]y[j2]f[j1]f[j2]>2g[i]
左边像斜率的表达式吧,让它等于 slop(j1,j2) 好了。
也就是说 x y优,当且仅当 slop(x,y)>2g[i]
我们按 j 的顺序维护一个队列,这个队列要满足如下性质:
一、slop(x,y)>2g[i],x<y
证明:若 slop(x,y)<2g[i] ,那么对于 i 以后的状态,由于g单增,所以这个不等式恒成立,那么 y 恒比x优,故可以除掉 x
二、slop(x,y)<slop(y,z),x<y<z
证明:若 slop(x,y)>slop(y,z) ,且 y 在某一个状态是最优的,有
slop(x,y)<2g[i],slop(y,z)>2g[i]slop(y,z)>slop(x,y)
于是我们维护一个相邻两元素斜率递增且均大于 2g[i] 最优状态可在队首取到。
那么代码就是这样的辣~(≧▽≦)/~啦啦啦

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define sqr(_) ((_)*(_))
#define N 55000
#define DB double
using namespace std;
int n;
long long c,sum[N],f[N],dp[N],g[N];
int q[N];
inline void read()
{
    scanf("%d%lld",&n,&c); c+=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&sum[i]);
        sum[i]+=sum[i-1];
        f[i]=sum[i]+(long long)i;
        g[i]=i+sum[i]-c;
    }
}
inline long long S(int x,int y)
{
    return (f[x]-f[y]);
}
inline long long G(int x,int y)
{
    return dp[x]-dp[y]+sqr(f[x])-sqr(f[y]);
}
DB slope(int x,int y)
{
    return (DB)G(x,y)/(DB)S(x,y);
}
inline void go()
{
    int first=0,last=0;
    for(int i=1;i<=n;i++)
    {
        while(last-first>1&&slope(q[last-2],q[last-1])>slope(q[last-1],i-1))
            last--;
        q[last++]=i-1;
        while(last-first>1&&slope(q[first],q[first+1])<(DB)2*g[i])first++;
        dp[i]=dp[q[first]]+sqr(i-q[first]+sum[i]-sum[q[first]]-c);
    }
    printf("%lld\n",dp[n]);
}
int main()
{
    read();
    go();
    return 0;
}

特别行动队

同上题,设同样的状态,有:
dp[i]=max(dp[j]+a(s[i]s[j])2+b(s[i]s[j]))+c
=max(dp[j]+as2[j]2as[i]s[j]+as2[j]+bs[i]bs[j])+c
f[i]=as2[i]bs[i]+dp[i],g[i]=as2[i]+bs[i]2as[i]s[j]
原式 =max(f[j]2as[i]s[j])+g[i]+c
x<y ,那么 x y优当且仅当:
f[x]2as[i]s[x]>f[y]2as[i]s[y]
f[x]f[y]>2as[i](s[x]s[y])
f[x]f[y]s[x]s[y]<2as[i]
同样地,我们按 j 的顺序维护一个队列,这个队列要满足如下性质:
一、slop(x,y)<2as[i],x<y
证明:若 slop(x,y)>2as[i] 等价于 slop(y,x)<2as[i] ,那么对于 i 以后的状态,由于s单增,所以这个不等式恒成立,那么 y 恒比x优,故可以除去 x
这条性质决定了队列里标号小的比标号大的优,所以答案自然是队首啦,上一题也是这个道理,冬令营的课件和我完全相反,大家可以思考一下为什么我的也是对的,这个我真的强烈建议,想出来的话你也就可以说是会这种类型的斜率优化了。
二、slop(x,y)>slop(y,z),x<y<z
证明:若 slop(x,y)<slop(y,z) ,且 y 在某一个状态是最优的,有
slop(x,y)>2as[i],slop(y,z)<2as[i]slop(y,z)<slop(x,y)
于是我们维护一个相邻两元素斜率递减且均大于 2as[i] 最优状态可在队首取到。
那么代码就是这样的辣~(≧▽≦)/~啦啦啦
请读者仔细品味这两道题的区别。

#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<cassert>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<climits>
#define X first
#define Y second
#define DB double
#define MP make_pair
#define LL long long
#define pb push_back
#define sqr(_) ((_)*(_))
#define INF 0x3f3f3f3f
#define pii pair<int,int>
#define pdd pair<DB,DB>
#define ull unsigned LL
#define DEBUG(...) fprintf(stderr,__VA_ARGS__)
using namespace std;
const int MAXN=1000010;
LL dp[MAXN],s[MAXN],a,b,c,w[MAXN];
int n,q[MAXN],first,last;
LL F(int x)
{
    return a*sqr(s[x])-b*s[x]+dp[x];
}
DB slope(int x,int y)
{
    return DB((DB)(F(x)-F(y)))/((DB)(s[x]-s[y]));
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("sp.in","r",stdin);
    freopen("sp.out","w",stdout);
#endif
    scanf("%d",&n);
    scanf("%lld %lld %lld",&a,&b,&c);
    q[last++]=0;
    for(int i=1;i<=n;i++)
        scanf("%lld",&w[i]),s[i]=w[i]+s[i-1];
    dp[1]=a*sqr(w[1])+b*w[1]+c;
    for(int i=2;i<=n;i++)
    {
        while(last-first>1&&slope(q[last-2],q[last-1])<slope(q[last-1],i-1))last--;
        q[last++]=i-1;
        while(last-first>1&&slope(q[first],q[first+1])>(DB)2*a*s[i])first++;
        dp[i]=F(q[first])-2*a*s[i]*s[q[first]]+a*sqr(s[i])+b*s[i]+c;
    }
    printf("%lld\n",dp[n]);
}

上文我提到“这一种类型的斜率优化”,没错,因为这里的斜率表达式的右边有单调性,分母也有单调性,如果没有的话,你用我的模板做题试试?
~所以,未完待续咯...
刚刚搞完冬令营的这张PPT,我来更新一下这篇博客,讲一下斜率优化的第二大类问题,斜率表达式的右边不具有单调性,分母也没有单调性,以NOI2007货币兑换作为例题。

货币兑换

上方程:
dp[i]=max(dp[i1],dp[j]×Ratej×Ai+BiRatej×Aj+Bj),1ji1
直接暴力dp是 n2 的,显然超时。
考虑斜率优化。
g(i)=dp[j]Ratej×Aj+Bj
max 右边 =g(j)×(Ratej×Ai+Bi)=g(j)×Ratej×Ai+g(j)×Bi
考虑对于一个 i ,有两个决策x y ,看在什么情况下x优于 y
g(x)×Ratex×Ai+g(x)×Bi>g(y)×Ratey×Ai+g(y)×Bi
Ai×(g(x)×Ratexg(y)×Ratey)>Bi×(g(y)g(x))
g(x)×Ratexg(y)×Ratey>BiAi×(g(y)g(x))
g(y)g(x)<0g(x)<g(y)
T(x,y)=g(x)×Ratexg(y)×Rateyg(x)(g(y))>BiAi
g(y)=g(x)
RatexRatey×BiAi>BiAi
以上便是条件。
我们可以看到对于一个决策,我们相当于插入了一个 (g(x),g(x)Ratex) 的点,我们维护一个以横坐标为关键字的平衡树,那么就是在这棵树上找决策就可以了。
由上面的推导我们有对于横坐标递增的个决策 x,y ,若 T(x,y)<BiAi x y优。
根据前两题的经验,我们要维护一个相邻决策斜率单调递减的splay(可以自己证明),那么我们的答案呢?是splay的根?
不是。
我们需要在splay上找,找一个 T(x,y)>BiAi,T(y,z)<BiAi,x,y,z 是相邻的, y 即就是答案。
基于这样一个证明:若T(x,y)>BiAi,那么 y x优,同理 y z优,又因为斜率是单调递减的,那么 y 比任何决策都优。
再来讲讲插入,我们如果要插入一个决策y,先将其按横坐标插入到splay中,旋到根,设其两个相邻的决策为 x z,若 T(x,y)<T(y,z) 表明斜率出现了增的趋势,那就直接把 y 删了。
插入了这个决策后,我们还要维护决策斜率的单调性,先维护这个决策左边的单调性,设y左边的两个决策为 a b,若 T(a,b)<T(b,y) 则删除 b ,再取a左边一个决策继续进行比较,删除,直到满足递减即可。维护右边同理。
还有一些细节问题,就是取等号的问题,也就是上面的 g(x)=g(y) 导致的,我不打算再讲了,反正我改着改着就过了,我也知道我的代码肯定还有漏洞,肯定还能被一些丧心病狂的人卡。
我用AVL伪装,纯属无聊。

#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<cassert>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<climits>
#define X first
#define Y second
#define DB double
#define MP make_pair
#define LL long long
#define pb push_back
#define lc son[now][0]
#define rc son[now][1]
#define sqr(_) ((_)*(_))
#define INF 0x3f3f3f3f
#define pii pair<int,int>
#define pdd pair<DB,DB>
#define ull unsigned LL
#define DEBUG(...) fprintf(stderr,__VA_ARGS__)
const int MAXN=100100;
const DB eps=1e-9;
int n;
DB s,A[MAXN],B[MAXN],Rate[MAXN],dp[MAXN];
DB max(DB x,DB y)
{
    return x>y?x:y;
}
DB fabs(DB x)
{
    return x<0?-x:x;
}
int dcmp(DB x)
{
    if(fabs(x)<eps)
        return 0;
    return x>0?1:-1;
}
struct Splay{
    int size[MAXN],son[MAXN][2],fa[MAXN],root;
    DB X[MAXN];
    Splay()
    {
        memset(size,0,sizeof(size));
        memset(son,0,sizeof(son));
        memset(fa,0,sizeof(fa));
        memset(X,0,sizeof(X));
        root=0;
    }
    DB G(int x)
    {
        return dp[x]/(Rate[x]*A[x]+B[x]);
    }
    DB slope(int x,int y,int k)
    {
        DB gx=-X[x],gy=-X[y];
        if(fabs(gx-gy)<eps)
            return Rate[x]/Rate[y]*B[k]/A[k];
        return (gx*Rate[x]-gy*Rate[y])/(X[x]-X[y]);
    }
    void rotate(int x)
    {
        int y=fa[x],d=(son[y][1]==x);
        son[fa[y]][y==son[fa[y]][1]]=x;
        fa[x]=fa[y];
        fa[son[x][d^1]]=y;
        son[y][d]=son[x][d^1];
        son[x][d^1]=y;
        fa[y]=x;
    }
    void splay(int x,int f)
    {
        while(fa[x]!=f)
        {
            int y=fa[x];
            if(fa[y]==f)
            {
                rotate(x);
                break;
            }
            if((son[fa[y]][1]==fa[y])^(son[fa[x]][1]==x))
            {
                rotate(y);
                rotate(x);
            }
            else
            {
                rotate(x);
                rotate(x);
            }
        }
        if(f==0)
            root=x;
    }
    int pre(int be)
    {
        int now=son[be][0],tmp=0;
        while(now)
            tmp=now,now=son[now][1];
        return tmp;
    }
    int post(int be)
    {
        int now=son[be][1],tmp=0;
        while(now)
            tmp=now,now=son[now][0];
        return tmp;
    }
    void Insert(int k,int i)
    {
        X[k]=-G(k);
        int now=root,tmp=0,d=0;
        while(now)
        {
            if(dcmp(X[now]-X[k])>0)
                tmp=now,now=son[now][0],d=0;
            else
                tmp=now,now=son[now][1],d=1;
        }
        son[tmp][d]=k;
        fa[k]=tmp;
        splay(k,0);
        maintain(i);
    }
    void del(int k)
    {
        splay(k,0);
        if(son[k][0]==0||son[k][1]==0)
        {
            int ch=son[k][0]+son[k][1];
            if(ch)fa[ch]=0;
            if(son[k][0])son[k][0]=0;
            else if(son[k][1])son[k][1]=0;
            return;
        }
        int Pre=pre(root);
        son[Pre][1]=son[k][1];
        fa[son[k][1]]=Pre;
        son[k][0]=son[k][1]=fa[son[k][0]]=0;
        splay(Pre,0);
    }
    void maintain(int i)
    {
        int now=root,x1=pre(root),x2=post(root),o=root;
        if(x1&&x2)
        {
            if(dcmp(slope(x1,root,i)-slope(root,x2,i))<0)
            {
                del(root);
                return;
            }
        }
        while(1)
        {
            x1=pre(root);
            if(x1==0)break;
            splay(x1,0);
            x2=pre(root);
            if(x2==0)break;
            if(dcmp(slope(x2,x1,i)-slope(x1,now,i))<0)
            {
                del(x1);
                now=root;
            }
            else break;
        }
        now=o;
        splay(o,0);
        while(1)
        {
            x1=post(root);
            if(x1==0)break;
            splay(x1,0);
            x2=post(root);
            if(x2==0)break;
            if(dcmp(slope(now,x1,i)-slope(x1,x2,i))<0)
            {
                del(x1);
                //splay(x2,0);
                now=root;
            }
            else break;
        }
    }    
    int find(int i)
    {
        /*if(i==42722)
            splay(42715,0);*/
        int now=root;
        while(1)
        {
            //splay(now,0);
            int j1=pre(now),j2=post(now);
            if(!j1&&!j2)return now;
            DB a=slope(j1,now,i),b=slope(now,j2,i);
            if(!j1)
            {
                if(dcmp(b-B[i]/A[i])<0)
                        return now;
                else now=son[now][1];
            }
            else if(!j2)
            {
                if(dcmp(a-B[i]/A[i])>0)
                    return now;
                else now=son[now][0];
            }
            else if(dcmp(a-B[i]/A[i])>0&&dcmp(b-B[i]/A[i])<=0)
                return now;
            else if(dcmp(b-B[i]/A[i])>0)
                now=son[now][1];
            else if(dcmp(a-B[i]/A[i])<=0)
                now=son[now][0];
        }
    }
    void print(int now)
    {
        if(!now)return;
        printf("%d ",now);
        print(son[now][0]);
        print(son[now][1]);
    }
}AVL;
int main()
{
#ifndef ONLINE_JUDGE
    freopen("cash.in","r",stdin);
    freopen("cash.out","w",stdout);
#endif
    scanf("%d %lf",&n,&s);
    for(int i=1;i<=n;i++)
        scanf("%lf %lf %lf",&A[i],&B[i],&Rate[i]);
    dp[1]=s;
    for(int i=2;i<=n;i++)
    {
        //DEBUG("%d\n",i);
        AVL.Insert(i-1,i);
        //printf("size::%d\n",AVL.size[AVL.root]);
        //AVL.print(AVL.root);
        int now=AVL.find(i);
        //printf("\n");
        dp[i]=max(dp[i-1],AVL.G(now)*Rate[now]*A[i]+AVL.G(now)*B[i]);
    }
    /*for(int i=1;i<=n;i++)
    printf("%.3f\n",dp[i]);*/
    printf("%.3lf\n",dp[n]);
}

至此便搞完了斜率优化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值