今天因为刷不动ydc的题,只能搞搞LCT和斜率优化,上午搞了LCT,下午搞斜率优化,其实我本来是想看冬令营的课件的,发现正好有斜率优化,也写得很好。
玩具装箱
直接上方程好了。
设
dp[i]
表示前
i
个玩具的答案,记
dp[i]=min(dp[j]+(i−j+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
比
我们按
j
的顺序维护一个队列,这个队列要满足如下性质:
一、
证明:若
slop(x,y)<2g[i]
,那么对于
i
以后的状态,由于
二、
证明:若
slop(x,y)>slop(y,z)
,且
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
比
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]
等价于
slop(y,x)<2as[i]
,那么对于
i
以后的状态,由于
这条性质决定了队列里标号小的比标号大的优,所以答案自然是队首啦,上一题也是这个道理,冬令营的课件和我完全相反,大家可以思考一下为什么我的也是对的,这个我真的强烈建议,想出来的话你也就可以说是会这种类型的斜率优化了。
二、
证明:若
slop(x,y)<slop(y,z)
,且
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[i−1],dp[j]×Ratej×Ai+BiRatej×Aj+Bj),1≤j≤i−1
直接暴力dp是
n2
的,显然超时。
考虑斜率优化。
设
g(i)=dp[j]Ratej×Aj+Bj
则
max
右边
=g(j)×(Ratej×Ai+Bi)=g(j)×Ratej×Ai+g(j)×Bi
考虑对于一个
i
,有两个决策
Ai×(g(x)×Ratex−g(y)×Ratey)>Bi×(g(y)−g(x))
g(x)×Ratex−g(y)×Ratey>BiAi×(g(y)−g(x))
若
g(y)−g(x)<0⇔−g(x)<−g(y)
T(x,y)=g(x)×Ratex−g(y)×Ratey−g(x)−(−g(y))>BiAi
若
g(y)=g(x)
RatexRatey×BiAi>BiAi
以上便是条件。
我们可以看到对于一个决策,我们相当于插入了一个
(−g(x),g(x)∗Ratex)
的点,我们维护一个以横坐标为关键字的平衡树,那么就是在这棵树上找决策就可以了。
由上面的推导我们有对于横坐标递增的个决策
x,y
,若
T(x,y)<BiAi
则
x
比
根据前两题的经验,我们要维护一个相邻决策斜率单调递减的splay(可以自己证明),那么我们的答案呢?是splay的根?
不是。
我们需要在splay上找,找一个
T(x,y)>BiAi,T(y,z)<BiAi,x,y,z
是相邻的,
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[n]);
}
至此便搞完了斜率优化。