您的位置:首页 > 其它

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

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

玩具装箱

直接上方程好了。

设dp[i]dp[i]表示前ii个玩具的答案,记s[i]s[i]为物品前缀和,那么:

dp[i]=min(dp[j]+(i−j+s[i]−s[j]−L)2),(j<i)dp[i]=min(dp[j]+(i-j+s[i]-s[j]-L)^2),(j这个很显然吧。

定义g[i]=i+s[i]−L,f[i]=i+s[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]dp[i]=min(dp[j]+(g[i]-f[j])^2)
=min(dp[j]+g^2[i]-2g[i]\times f[j]+f^2[j])
=min(dp[j]+f^2[j]-2g[i]\times f[j])+g^2[i]

现在我们考虑,对于一个j1<j2j_1,j1j_1比j2j_2优的条件是什么。

显然:

dp[j1]+f2[j1]−2g[i]×f[j1]<dp[j2]+f2[j2]−2g[i]×f[j2]dp[j_1]+f^2[j_1]-2g[i]\times f[j_1]

设y[i]=dp[i]+f2[i]y[i]=dp[i]+f^2[i]

y[j1]−y[j2]<2g[i](f[j1]−f[j2])y[j_1]-y[j_2]<2g[i](f[j_1]-f[j_2])

y[j1]−y[j2]f[j1]−f[j2]>2g[i]\frac{y[j_1]-y[j_2]}{f[j_1]-f[j_2]}>2g[i]

左边像斜率的表达式吧,让它等于slop(j1,j2)slop(j_1,j_2)好了。

也就是说xx比yy优,当且仅当slop(x,y)>2g[i]slop(x,y)>2g[i]

我们按jj的顺序维护一个队列,这个队列要满足如下性质:

一、slop(x,y)>2g[i],x<yslop(x,y)>2g[i],x

证明:若slop(x,y)<2g[i]slop(x,y)<2g[i],那么对于ii以后的状态,由于gg单增,所以这个不等式恒成立,那么yy恒比xx优,故可以除掉xx。

二、slop(x,y)<slop(y,z),x<y<zslop(x,y)

证明:若slop(x,y)>slop(y,z)slop(x,y)>slop(y,z),且yy在某一个状态是最优的,有

slop(x,y)<2g[i],slop(y,z)>2g[i]⇒slop(y,z)>slop(x,y)矛盾slop(x,y)<2g[i],slop(y,z)>2g[i]\Rightarrow slop(y,z)>slop(x,y)矛盾

于是我们维护一个相邻两元素斜率递增且均大于2g[i]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
,f
,dp
,g
;
int q
;
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
);
}
int main()
{
read();
go();
return 0;
}


特别行动队

同上题,设同样的状态,有:

dp[i]=max(dp[j]+a(s[i]−s[j])2+b(s[i]−s[j]))+cdp[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=max(dp[j]+as^2[j]-2as[i]s[j]+as^2[j]+bs[i]-bs[j])+c

设f[i]=as2[i]−bs[i]+dp[i],g[i]=as2[i]+bs[i]−2as[i]s[j]f[i]=as^2[i]-bs[i]+dp[i],g[i]=as^2[i]+bs[i]-2as[i]s[j]

原式=max(f[j]−2as[i]s[j])+g[i]+c=max(f[j]-2as[i]s[j])+g[i]+c

设x<yx,那么xx比yy优当且仅当:

f[x]−2as[i]s[x]>f[y]−2as[i]s[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]>2as[i](s[x]-s[y])

f[x]−f[y]s[x]−s[y]<2as[i]\frac{f[x]-f[y]}{s[x]-s[y]}<2as[i]

同样地,我们按jj的顺序维护一个队列,这个队列要满足如下性质:

一、slop(x,y)<2as[i],x<yslop(x,y)<2as[i],x

证明:若slop(x,y)>2as[i]slop(x,y)>2as[i]等价于slop(y,x)<2as[i]slop(y,x)<2as[i],那么对于ii以后的状态,由于ss单增,所以这个不等式恒成立,那么yy恒比xx优,故可以除去xx。

这条性质决定了队列里标号小的比标号大的优,所以答案自然是队首啦,上一题也是这个道理,冬令营的课件和我完全相反,大家可以思考一下为什么我的也是对的,这个我真的强烈建议,想出来的话你也就可以说是会这种类型的斜率优化了。

二、slop(x,y)>slop(y,z),x<y<zslop(x,y)>slop(y,z),x

证明:若slop(x,y)<slop(y,z)slop(x,y),且yy在某一个状态是最优的,有

slop(x,y)>2as[i],slop(y,z)<2as[i]⇒slop(y,z)<slop(x,y)矛盾slop(x,y)>2as[i],slop(y,z)<2as[i]\Rightarrow slop(y,z)

于是我们维护一个相邻两元素斜率递减且均大于2as[i]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
);
}


上文我提到“这一种类型的斜率优化”,没错,因为这里的斜率表达式的右边有单调性,分母也有单调性,如果没有的话,你用我的模板做题试试?

~所以,未完待续咯...

刚刚搞完冬令营的这张PPT,我来更新一下这篇博客,讲一下斜率优化的第二大类问题,斜率表达式的右边不具有单调性,分母也没有单调性,以NOI2007货币兑换作为例题。

货币兑换

上方程:

dp[i]=max(dp[i−1],dp[j]×Ratej×Ai+BiRatej×Aj+Bj),1≤j≤i−1dp[i]=max(dp[i-1],dp[j]\times \frac{Rate_j\times A_i+Bi}{Rate_j\times A_j+B_j}),1\le j\le i-1

直接暴力dp是n2n^2的,显然超时。

考虑斜率优化。

设g(i)=dp[j]Ratej×Aj+Bjg(i)=\frac{dp[j]}{Rate_j\times A_j+B_j}

则maxmax右边=g(j)×(Ratej×Ai+Bi)=g(j)×Ratej×Ai+g(j)×Bi=g(j)\times (Rate_j\times A_i+B_i)=g(j)\times Rate_j\times A_i+g(j)\times B_i

考虑对于一个ii,有两个决策xx和yy,看在什么情况下xx优于yy。

g(x)×Ratex×Ai+g(x)×Bi>g(y)×Ratey×Ai+g(y)×Big(x)\times Rate_x\times A_i+g(x)\times B_i>g(y)\times Rate_y\times A_i+g(y)\times B_i

Ai×(g(x)×Ratex−g(y)×Ratey)>Bi×(g(y)−g(x))A_i\times(g(x)\times Rate_x-g(y)\times Rate_y)>B_i\times (g(y)-g(x))

g(x)×Ratex−g(y)×Ratey>BiAi×(g(y)−g(x))g(x)\times Rate_x-g(y)\times Rate_y>\frac{B_i}{A_i}\times (g(y)-g(x))

若g(y)−g(x)<0⇔−g(x)<−g(y)g(y)-g(x)<0\Leftrightarrow -g(x)<-g(y)

T(x,y)=g(x)×Ratex−g(y)×Ratey−g(x)−(−g(y))>BiAiT(x,y)=\frac{g(x)\times Rate_x-g(y)\times Rate_y}{-g(x)-(-g(y))}>\frac{B_i}{A_i}

若g(y)=g(x)g(y)=g(x)

RatexRatey×BiAi>BiAi\frac{Rate_x}{Rate_y}\times \frac{B_i}{A_i}> \frac{B_i}{A_i}

以上便是条件。

我们可以看到对于一个决策,我们相当于插入了一个(−g(x),g(x)∗Ratex)(-g(x),g(x)*Rate_x)的点,我们维护一个以横坐标为关键字的平衡树,那么就是在这棵树上找决策就可以了。

由上面的推导我们有对于横坐标递增的个决策x,yx,y,若T(x,y)<BiAiT(x,y)<\frac{B_i}{A_i}则xx比yy优。

根据前两题的经验,我们要维护一个相邻决策斜率单调递减的splay(可以自己证明),那么我们的答案呢?是splay的根?

不是。

我们需要在splay上找,找一个T(x,y)>BiAi,T(y,z)<BiAi,x,y,zT(x,y)>\frac{B_i}{A_i},T(y,z)<\frac{B_i}{A_i},x,y,z是相邻的,yy即就是答案。

基于这样一个证明:若T(x,y)>BiAiT(x,y)>\frac{B_i}{A_i},那么yy比xx优,同理yy比zz优,又因为斜率是单调递减的,那么yy比任何决策都优。

再来讲讲插入,我们如果要插入一个决策yy,先将其按横坐标插入到splay中,旋到根,设其两个相邻的决策为xx,zz,若T(x,y)<T(y,z)T(x,y)表明斜率出现了增的趋势,那就直接把yy删了。

插入了这个决策后,我们还要维护决策斜率的单调性,先维护这个决策左边的单调性,设yy左边的两个决策为aa,bb,若T(a,b)<T(b,y)T(a,b)则删除bb,再取aa左边一个决策继续进行比较,删除,直到满足递减即可。维护右边同理。

还有一些细节问题,就是取等号的问题,也就是上面的g(x)=g(y)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
);
}


至此便搞完了斜率优化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: