bzoj3437小P的农场
题
http://www.lydsy.com/JudgeOnline/problem.php?id=3437
题解
总述
方程不难写出
fi=max{fj+w(j,i)} j∈[0,i)
其中 w(j,i) 表示前 i 个牧场中,在
这里的w可以用两个前缀和求出,
s2[i]=1×a[i−1]+2×a[i−2]+3×a[i−3]+...+(i−1)×a[1]
那么 w(l,r)=s2[r]−s2[l]−(r−l)∗s1[l]
这样求w是 O(1) 的
所以总递推是 O(N2) 的,不能满足要求
优化方法一 决策单调性加速递推
输出决策:
根据wyw第一定律,我们玄学地认为,这个递推满足决策单调性
输出决策后发现他们总是挨得很近,所以抱着试一试的心理,我们加入不稳定的优化,玄学算法:即记录当前决策,下次直接从上一个决策往后找。
接下来是稳定的算法:
二分算法维护一个栈,存一个决策的范围,然后每次加入新决策就二分寻找断点,时间复杂度
O(Nlog2N)
优化方法二 斜率优化
fi=fj+s2[i]−s2[j]−(i−j)×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=yi−i×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;
}