BZOJ 1492: [NOI2007]货币兑换Cash CDQ分治+斜率优化

title

BZOJ 1492

LUOGU 4027

Description

img

Input

第一行两个正整数N、S,分别表示小Y 能预知的天数以及初始时拥有的钱数。 接下来N 行,第K 行三个实数AK、BK、RateK,意义如题目中所述

Output

只有一个实数MaxProfit,表示第N 天的操作结束时能够获得的最大的金钱 数目。答案保留3 位小数。

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT

img测试数据设计使得精度误差不会超过10-7。
对于40%的测试数据,满足N ≤ 10;
对于60%的测试数据,满足N ≤ 1 000;
对于100%的测试数据,满足N ≤ 100 000;

analysis

首先又一个 \(O(N^2)Dp\) 可以直接写,根据贪心:每天要不全买,要不全卖。

那么就有状态转移方程:
\[ F[i]=max(x[j]*a[i]+y[j]*b[i],F[i-1]) \]

其中 \(x[i]\) 表示第 \(i\) 天最多能获得的 \(A\) 卷数量,\(y[i]\) 表示 \(B\) 卷数量。
那么
\[ x[i]=F[i]/(A[i]∗Rate[i]+B[i])∗Rate[i]\\ y[i]=F[i]/(A[i]∗Rate[i]+B[i]) \]

好,现在我们考虑优化。

\(j\) 是最优决策,那么
\[ F[i]=x[j]∗a[i]+y[j]∗b[i] \]

于是
\[ y[j]=−\frac{a[i]}{b[i]}x[j]+\frac{F[i]}{b[i]} \]

发现这是一次函数的形式。我们想让截距最大。

于是我们可以维护一个凸包,因为斜率一定,使截距最大的点一定在凸包上。

\(x\)\(x\) 轴,\(y\)\(y\) 轴建立平面直角坐标系。 但是,\(x[i]\)\(-\frac{a[i]}{b[i]}\) 不一定单调。

所以我们就是要动态维护一个凸包,然后求某个斜率的位置。

\(CDQ\) 分治可以解决这个问题,当然,\(Splay\) 也可以,不过不想写。

code

\(60pts\) 暴力 \(Dp\)

#include<bits/stdc++.h>

const int maxn=1e5+10;
typedef double darr[maxn];

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
}

using IO::read;

template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }

darr A,B,Rate,f;
int main()
{
    int n,s;read(n);read(s);
    for (int i=1; i<=n; ++i) scanf("%lf%lf%lf",&A[i],&B[i],&Rate[i]);
    double ans=s;
    f[1]=s*Rate[1]/(Rate[1]*A[1]+B[1]);
    for (int i=2; i<=n; ++i)
    {
        for (int j=1; j<i; ++j) chkMax(ans,f[j]*A[i]+f[j]/Rate[j]*B[i]);
        f[i]=ans*Rate[i]/(Rate[i]*A[i]+B[i]);
    }
    printf("%.3f\n",ans);
    return 0;
}

\(CDQ\) 分治维护斜率优化 \(Dp\)

#include<bits/stdc++.h>

const int maxn=1e5+10;
const double eps=1e-9,inf=1e20;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }
}

using IO::read;

template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }

double f[maxn];
struct Orz{double x,y,k,A,B,Rate; int w,id;}o[maxn],t[maxn];
inline bool operator < (Orz a,Orz b)
{
    return a.k>b.k;
}

inline double slope(int a,int b)
{
    if (!b) return -inf;
    if (fabs(o[a].x-o[b].x)<eps) return inf;
    return (o[b].y-o[a].y)/(o[b].x-o[a].x);
}

int Stack[maxn],top;
inline void CDQ(int l,int r)
{
    if (l==r)//分治到底了显然我们可以直接计算出结果
    {
        chkMax(f[l],f[l-1]);
        o[l].y=f[l]/(o[l].A*o[l].Rate+o[l].B);
        o[l].x=o[l].Rate*o[l].y;
        return ;
    }
    int mid=(l+r)>>1, l1=l, l2=mid+1, j=1;
    for (int i=l; i<=r; ++i)
        if (o[i].id<=mid) t[l1++]=o[i];
        else t[l2++]=o[i];
    for (int i=l; i<=r; ++i) o[i]=t[i];
    CDQ(l,mid);//这一部分是要将一块原顺序分为左右两块
    top=0;//递归左边
    for (int i=l; i<=mid; ++i)
    {
        while (top>1 && slope(Stack[top-1],Stack[top])<slope(Stack[top-1],i)+eps) --top;
        Stack[++top]=i;
    }//左边维护一个凸包
    Stack[++top]=0;
    for (int i=mid+1; i<=r; ++i)
    {
        while (j<top && slope(Stack[j],Stack[j+1])+eps>o[i].k) ++j;//用左边的点作为决策更新右边
        chkMax(f[o[i].id],o[Stack[j]].x*o[i].A+o[Stack[j]].y*o[i].B);
    }
    CDQ(mid+1,r);//递归右边
    l1=l, l2=mid+1;
    for (int i=l; i<=r; ++i)
        if ( ((o[l1].x<o[l2].x || (fabs(o[l1].x-o[l2].x)<eps && o[l1].y<o[l2].y)) || l2>r) && l1<=mid) t[i]=o[l1++];
        else t[i]=o[l2++];
    for (int i=l; i<=r; ++i) o[i]=t[i];
}

int main()
{
    int n;read(n);scanf("%lf",&f[0]);
    for (int i=1; i<=n; ++i) scanf("%lf%lf%lf",&o[i].A,&o[i].B,&o[i].Rate), o[i].k=-o[i].A/o[i].B, o[i].id=i;
    std::sort(o+1,o+n+1);//按照斜率进行排序,保证分治的每一块斜率是有序的
    CDQ(1,n);
    printf("%.3f\n",f[n]);
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11405762.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值