bzoj 3437: 小P的牧场

bzoj3437小P的农场

http://www.lydsy.com/JudgeOnline/problem.php?id=3437

题解

总述

  方程不难写出

fi=max{fj+w(j,i)} j[0,i)

  其中 w(j,i) 表示前 i 个牧场中,在i处建立最后一个检查站,在 j 处建立倒数第二个检查站的最小化费
  这里的w可以用两个前缀和求出,s1[i]=b[i]
   s2[i]=1×a[i1]+2×a[i2]+3×a[i3]+...+(i1)×a[1]
  那么 w(l,r)=s2[r]s2[l](rl)s1[l]
  这样求w是 O(1)
  所以总递推是 O(N2) 的,不能满足要求

优化方法一 决策单调性加速递推

  输出决策:
  测试决策单调性
  根据wyw第一定律,我们玄学地认为,这个递推满足决策单调性
  输出决策后发现他们总是挨得很近,所以抱着试一试的心理,我们加入不稳定的优化,玄学算法:即记录当前决策,下次直接从上一个决策往后找。
  接下来是稳定的算法:
  二分算法维护一个栈,存一个决策的范围,然后每次加入新决策就二分寻找断点,时间复杂度 O(Nlog2N)

优化方法二 斜率优化

  

fi=fj+s2[i]s2[j](ij)×s1[j]

  化简整理
  
fi=(fj+j×s1[j]s2[j])i×s1[j]+s2[i]

  令 yj=fj+j×s1[j]s2[j] xj=s1[j]
  那么
  
fi=yii×xj+s2[i]

  斜率为i,是单调递增的,维护下凸壳。时间复杂度 O(N)

三种算法的比较

  运行结果
  看来玄学最快,斜率优化第二,再次是二分
  其实所谓的玄学算法应该是可以证明的吧,直觉告诉我,控制的代价随着距离的增加会逐渐增加,所以我们应该尽量让它控制的牧场少一些。。。有大牛会证吗?

代码

玄学

//决策单调性+玄学 
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 1000010
#define inf ((long long)1<<60)
#define ll long long
using namespace std;
ll a[maxn], b[maxn], s1[maxn], s2[maxn], N, f[maxn], c[maxn];
struct interval{ll l, r, c;}it[maxn];
void init()
{
    ll i;
    scanf("%lld",&N);
    for(i=1;i<=N;i++)scanf("%lld",a+i);
    for(i=1;i<=N;i++)scanf("%lld",b+i);
    for(i=1;i<=N;i++)s1[i]=s1[i-1]+b[i];
    for(i=2;i<=N;i++)s2[i]=s2[i-1]+s1[i-2]+b[i-1];
}
ll w(ll l, ll r){return s2[r]-s2[l]-(r-l)*s1[l];}
void work()
{
    ll i, j;
    for(i=1;i<=N;i++)
    {
        f[i]=inf;
        for(j=c[i-1];j<i;j++)
        {
            if(f[j]+w(j,i)<f[i])f[i]=f[j]+w(j,i),c[i]=j;
        }
        f[i]+=a[i];
    }
}
int main()
{
    init();
    work();
    printf("%lld\n",f[N]);
    return 0;
}

单调栈+二分

//决策单调性+二分 
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 1000010
#define inf ((long long)1<<60)
#define ll long long
using namespace std;
ll a[maxn], b[maxn], s1[maxn], s2[maxn], N, f[maxn], top;
struct interval{ll l, r, c;}s[maxn];
void init()
{
    ll i;
    scanf("%lld",&N);
    for(i=1;i<=N;i++)scanf("%lld",a+i);
    for(i=1;i<=N;i++)scanf("%lld",b+i);
    for(i=1;i<=N;i++)s1[i]=s1[i-1]+b[i];
    for(i=2;i<=N;i++)s2[i]=s2[i-1]+s1[i-2]+b[i-1];
}
ll w(ll l, ll r){return l<r?s2[r]-s2[l]-(r-l)*s1[l]:inf;}
ll update(ll a, ll b){return f[a]+w(a,b);}
void work()
{
    ll l, r, mid, i, p, c;
    s[p=++top]=(interval){0,N,0};
    for(i=1;i<=N;i++)
    {
        while(s[p].r<i)p++;
        f[i]=update(s[p].c,i)+a[i];
        while(update(i,s[top].l) < update(s[top].c,s[top].l))top--;
        l=s[top].l,r=s[top].r,c=s[top].c,mid=l+r>>1;
        while(l<r)
        {
            if(update(i,mid) < update(c,mid))r=mid;
            else l=mid+1;
            mid=l+r>>1;
        }
        if(update(i,r) < update(s[top].c,r))s[top].r=r-1;
        if(s[top].r<N)s[top+1]=(interval){s[top].r+1,N,i},top++;
    }
}
int main()
{
    init();
    work();
    printf("%lld\n",f[N]);
    return 0;
}

斜率优化

//斜率优化 
#include <cstdio>
#include <algorithm>
#define inf ((long long)1<<60)
#define ll long long
#define  maxn 1000010
using namespace std;
ll a[maxn], b[maxn], s1[maxn], s2[maxn], N, f[maxn];
struct point{ll x, y;double k;}q[maxn];
void init()
{
    ll i;
    scanf("%lld",&N);
    for(i=1;i<=N;i++)scanf("%lld",a+i);
    for(i=1;i<=N;i++)scanf("%lld",b+i);
    for(i=1;i<=N;i++)s1[i]=s1[i-1]+b[i];
    for(i=2;i<=N;i++)s2[i]=s2[i-1]+s1[i-2]+b[i-1];
}
void work()
{
    ll i, x, y, l=1, r=1;
    q[r++]=(point){0,0,0};
    for(i=1;i<=N;i++)
    {
        while(l<r-1 and i>q[l+1].k)l++;
        f[i]=q[l].y-i*q[l].x+s2[i]+a[i];
        y=f[i]+i*s1[i]-s2[i];
        x=s1[i];
        while(r>l+1 and double(y-q[r-1].y)/(x-q[r-1].x)<q[r-1].k)r--;
        q[r]=(point){x,y,double(y-q[r-1].y)/(x-q[r-1].x)},r++;
    }
}
int main()
{
    init();
    work();
    printf("%lld\n",f[N]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值