[斜率优化入门][HNOI2008]玩具装箱/[BZOJ3156]防御准备/[NOI2007]货币兑换

斜率优化主要针对dp

当遇到dp[i]=min(a[i]*dp[j]+f[j]+b[i]+j)  此种类似情况时(也就是对于求i 我们要考虑选哪个j以致最值),考虑斜率优化

具体步骤: 考虑此时k优于j (k>j)

那么   a[i]*dp[k]+f[k]+b[i]+k<a[i]*dp[j]+f[j]+b[i]+j

  把i放在一边,j,k,放在一边  若dp[]单调递增 即-a[i]>(k+f[k]-j-f[j])/(k-j)时满足条件

将其考虑到平面上的点对A(a,a+f[a])、B(b,b+f[b])(a>b)

只有当AB的斜率<-a[i] a优于b

在此凸包中斜率单调上升, 而对于一个查询-a[i],我们要找到的,是不存在一个点k(<i && >j)能使kj斜率<-a[i],同时也不存在一个点k(<i && <j)能使kj斜率>-a[i]的点j,也就是说 斜率为-a[i]的直线与凸包切点(考虑凸包内的点一定不满足如上性质)

考虑对于每一个 i 都有一个固定的对应点

此时只需维护凸包即可

很多时候,根据题意,a[i]单调上升或单调下降,此时我们只要用队列维护一个凸包即可  (具体见图)

(凸包斜率单调,我们查询在队首,对于每一个i 维护当前凸包,新增点i在队尾,对于新增点,加入凸包,若前一点到此点的斜率<前一点到前一点的前一点的斜率,则说明前一点不在凸包内,将此点从队列尾部弹出)

[HNOI2008]玩具装箱/[BZOJ3156]防御准备 就都是这样的

以[BZOJ3156]防御准备 为例吧

看这里

#include<bits/stdc++.h>
using namespace std;
long long tmp,h,t,n,q[2000001],a[2000001],f[2000001];
long long  zhi(long long a) { return f[a]+(a*(a+1))/2;}
double  slop(long long  a,long long  b)
{   return 1.0*(zhi(a)-zhi(b))/(a-b);}
int main()
{
	cin>>n;
	for (int i=1;i<=n;i++)  cin>>a[i];
	  h=t=0;
	for (long long  i=1;i<=n;i++)
	{
	  while (h<t && slop(q[h+1],q[h])<=i) h++;
	  tmp=q[h];
	   f[i]=zhi(tmp)-i*tmp+a[i]+(i*(i-1)>>1);
	  while (h<t && slop(i,q[t])<=slop(q[t],q[t-1]))  {t--;}
	  q[++t]=i;
	}
	cout<<f[n]<<endl;
}/*一定要注意for循环里的i乘法运算爆int*/ 

那么a[i]不是单调的呢

[NOI2007]货币兑换

先考虑方程

易想 一天只可能全部买入或全部卖出

公式部分见qiqi课件

此时用cdq分治维护

对于后一半的值,可以用前一半的点构成的凸包对其进行更新

凸包的合并只需要按x的大小归并就行了(思路就是这样,感到抽象看代码)

#include<bits/stdc++.h>
using namespace std;
#define db double
#define inf 1e9
#define eps 1e-9
const int N=100005;
int n,s[N];db dp[N];
struct node{db k,x,y,a,b,r;int id;}Q[N],kl[N];
db getk(int i,int j) {
    if(fabs(Q[i].x-Q[j].x)<=eps) return inf;
    return (Q[j].y-Q[i].y)/(Q[j].x-Q[i].x);
}
void merge(int l,int r,int mid) {//归并排序
    int t1=l,t2=mid+1;
    for(int i=l;i<=r;++i)
        if(t1<=mid&&(t2>r||Q[t1].x<Q[t2].x+eps)) kl[i]=Q[t1],++t1;
        else kl[i]=Q[t2],++t2;
    for(int i=l;i<=r;++i) Q[i]=kl[i];
}
void cdq(int l,int r) {
    if(l==r) {//那么在l之前的所有询问都已经处理完毕,可以更新l的答案了
        dp[l]=max(dp[l],dp[l-1]);
        Q[l].y=dp[l]/(Q[l].a*Q[l].r+Q[l].b),Q[l].x=Q[l].y*Q[l].r;
        return;
    }
    int mid=(l+r)>>1,t1=l-1,t2=mid,top=0;
    for(int i=l;i<=r;++i)//把前mid个询问放在左边,后mid个放在右边
        if(Q[i].id<=mid) kl[++t1]=Q[i];
        else kl[++t2]=Q[i];
    for(int i=l;i<=r;++i) Q[i]=kl[i];
    cdq(l,mid);//递归处理左边
    for(int i=l;i<=mid;++i) {//维护斜率递减的凸包
        while(top>=2&&getk(s[top],i)+eps>getk(s[top-1],s[top])) --top;
        s[++top]=i;
    }
    for(int i=mid+1;i<=r;++i) {//处理右边的询问
        while(top>=2&&getk(s[top-1],s[top])<=Q[i].k+eps) --top;
        int j=s[top];
        dp[Q[i].id]=max(dp[Q[i].id],Q[j].x*Q[i].a+Q[j].y*Q[i].b);
    }
    cdq(mid+1,r),merge(l,r,mid);//递归处理右边后,按照x值为关键字归并排序
}
int cmp1(node t1,node t2) {return t1.k<t2.k;}
int main() 
{
    scanf("%d%lf",&n,&dp[0]);
    for(int i=1;i<=n;++i) {
        scanf("%lf%lf%lf",&Q[i].a,&Q[i].b,&Q[i].r);
        Q[i].k=-Q[i].a/Q[i].b,Q[i].id=i;
    }
    sort(Q+1,Q+1+n,cmp1),cdq(1,n);
    printf("%.3lf\n",dp[n]);
    return 0;
}

初次接触的基础理解吧。

猜你喜欢

转载自blog.csdn.net/zzrh2018/article/details/81660558