• 三分与单调队列优化DP例题


    三分:

    ([AHOI2014/JSOI2014]宅男计划)

    题目描述

    外卖店一共有N种食物,分别有1到N编号。第i种食物有固定的价钱Pi和保质期Si。第i种食物会在Si天后过期。JYY是不会吃过期食物的。

    比如JYY如果今天点了一份保质期为1天的食物,那么JYY必须在今天或者明天把这个食物吃掉,否则这个食物就再也不能吃了。保质期可以为0天,这样这份食物就必须在购买当天吃掉。

    JYY现在有M块钱,每一次叫外卖需要额外付给送外卖小哥外送费F元。

    送外卖的小哥身强力壮,可以瞬间给JYY带来任意多份食物。JYY想知道,在满足每天都能吃到至少一顿没过期的外卖的情况下,他可以最多宅多少天呢?

    输入输出格式

    输入格式:

    第一行包含三个整数M,F和N。

    接下来N行,第i行包含两个整数Pi和Si。

    输出格式:

    输出仅包含一行一个整数表示JYY可以宅的最多的天数。

    输入输出样例

    输入样例#1:

    32 5 2
    5 0
    10 2

    输出样例#1:

    3
    

    说明

    【样例说明】

    JYY的最佳策略是:

    第一天买一份食物1和一份食物2并且吃一份食物1;

    第二天吃一份食物2;

    第三天买一份食物1并且吃掉。

    【数据规模与约定】

    对于100%的数据满足0<=Si<=10^18,1<=F,Pi,M<=10^18,1<=N<=200

    题目地址:https://www.luogu.org/problemnew/show/P4040


    个人思路:

    • 第一眼看题面感觉是DP,但看看数据范围只能想到Miller-Rabin,但是实际上三分也OK
    • 可以发现在钱数固定的情况下,单次购买食物的数量与维持时间是一个单峰函数
    • 二分+三分即可求解
    • T=(M-cost(k)*c-c*f)/(cost(k+1)-cost(k))+k*c ,k=D((M-C*f)/C)

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    int n;int cnt;ll m;ll f;
    struct food
    {
        ll cst;ll kep;
        friend bool operator <(food a,food b){return a.kep<b.kep;}
    }tp[210],fo[210];
    inline ll cost(ll t)//计算存活到t的花费 
    {
        ll res=0;
        for(int i=1;i<=cnt;i++)
        {
            if(res<0){return -1;}
            if(t>fo[i].kep){res+=fo[i].cst*fo[i].kep;t-=fo[i].kep;}
            else {res+=fo[i].cst*t;return res;}
        }return -1;//这里是避免爆INF 
    }
    inline ll calcd(ll pm)//计算花多少钱可以存活多少天 
    {
        ll l=0;ll r=pm;
        while(l<r)//单调可以二分 
        {
            ll mid=(l+r+1)/2;ll y=cost(mid);
            if(y==-1||y>pm){r=mid-1;}else {l=mid;}
        }
        return r;
    }
    inline ll calct(ll r)//计算T 
    {
        ll rest=m-r*f;if(rest<0||rest>m){return 0;}//还是避免爆longlong 
        ll pm=rest/r;ll k=calcd(pm);if(k==0){return 0;} 
        ll c2=cost(k+1);ll c1=cost(k);if(c2==-1){return r*k;}
        else {rest-=c1*r;return rest/(c2-c1)+r*k;}
    }
    int main()
    {
        scanf("%lld%lld%d",&m,&f,&n);
        for(int i=1;i<=n;i++){scanf("%lld%lld",&tp[i].cst,&tp[i].kep);}
        sort(tp+1,tp+n+1);tp[0].kep=-1;fo[0].kep=-1;
        for(int i=1;i<=n;i++)//单调栈去掉无用的东西 
        {while(cnt!=0&&tp[i].cst<fo[cnt].cst){cnt--;}fo[++cnt]=tp[i];}
        for(int i=cnt;i>=1;i--){fo[i].kep-=fo[i-1].kep;}//后向差分方便计算 
        ll l=1;ll r=m/f+1;//三分法求函数最值 
        while(r-l>5)//缩小区间 
        {
            ll x1=l+(r-l)/3;ll x2=l+((r-l)*2)/3;
            ll y1=calct(x1);ll y2=calct(x2);
            if(y1<y2){l=x1;}else {r=x2;}
        }
        ll res=calct(l);//然后暴力枚举最大值 
        for(ll i=l+1;i<=r;i++){res=max(res,calct(i));}
        printf("%lld",res);return 0;//拜拜程序~ 
    }

    单调队列优化DP:

    ([SCOI2010]股票交易)

    题目描述

    最近 	ext{lxhgww} 又迷上了投资股票,通过一段时间的观察和学习,他总结出了股票行情的一些规律。

    通过一段时间的观察, 	ext{lxhgww} 预测到了未来 T 天内某只股票的走势,第 ii 天的股票买入价为每股 AP_i,第 i 天的股票卖出价为每股 BP_i​(数据保证对于每个 i,都有 AP_i geq BP_i​),但是每天不能无限制地交易,于是股票交易所规定第 i 天的一次买入至多只能购买 AS_i股,一次卖出至多只能卖出 BS_i股。

    另外,股票交易所还制定了两个规定。为了避免大家疯狂交易,股票交易所规定在两次交易(某一天的买入或者卖出均算是一次交易)之间,至少要间隔 W 天,也就是说如果在第 i 天发生了交易,那么从第 i+1天到第 i+W 天,均不能发生交易。同时,为了避免垄断,股票交易所还规定在任何时间,一个人的手里的股票数不能超过 	ext{MaxP}

    在第 1 天之前,	ext{lxhgww}手里有一大笔钱(可以认为钱的数目无限),但是没有任何股票,当然,T 天以后,	ext{lxhgww}想要赚到最多的钱,聪明的程序员们,你们能帮助他吗?

    输入输出格式

    输入格式:

    输入数据第一行包括 3 个整数,分别是 T,	ext{MaxP},W。

    接下来 T 行,第 i 行代表第 i-1 天的股票走势,每行 4 个整数,分别表示 AP_i, BP_i, AS_i, BS_i

    输出格式:

    输出数据为一行,包括 1 个数字,表示 	ext{lxhgww} 能赚到的最多的钱数。

    输入输出样例

    输入样例#1:

    5 2 0
    2 1 1 1
    2 1 1 1
    3 2 1 1
    4 3 1 1
    5 4 1 1

    输出样例#1: 

    3

    说明

    对于 100\%100% 的数据,0leq W<Tleq 2000,1leq	ext{MaxP}leq2000

    对于所有的数据,1leq BP_ileq AP_ileq 1000,1leq AS_i,BS_ileq	ext{MaxP}

    题目地址:https://www.luogu.org/problemnew/show/P2569


    个人思路:

    • 第三种情况的转移方程f_{i,j}=max(f_{i,j} \,, \,f_{i-w-1,k}+k	imes ap_i)-j	imes ap_i(j - as_i leqslant k < j)
      第四种情况的转移方程f_(i,j)​=max(f_{i,j} \,, \,f_{i-w-1,k}+k 	imes bp_i)-j 	imes bp_i (j < k leqslant j + bs_i)
    • 可以发现f_{i-w-1,k}+k	imes ap_i可由单调队列维护

    #include <cstdio>
    #include <cstring>
    #define Min(x , y) ((x) < (y) ? (x) : (y))
    #define Max(x , y) ((x) > (y) ? (x) : (y))
    
    int n , m , ap , bp , as , bs , w , ans = 0 , f[2001][2001] , l , r , q[2001];
    // f[i][j] 表示第 i 天后拥有 j 张股票赚的最多钱数
    // l , r , q[x] 用于单调队列
    
    int main(){
        scanf("%d%d%d" , &n , &m , &w);
        memset(f , 128 , sizeof(f)); // 128 实际上给 f 数组赋的是 -inf,可以试试看
        for(int i = 1 ; i <= n ; i++){
            scanf("%d%d%d%d" , &ap , &bp , &as , &bs);
            for(int j = 0 ; j <= as ; j++)
                f[i][j] = -1 * j * ap; // 第 1 种情况,直接赋
            for(int j = 0 ; j <= m ; j++)
                f[i][j] = Max(f[i][j] , f[i - 1][j]); // 第 2 种情况,转移
            if(i <= w)
                continue; // 因为第 3,4 种情况都有 i - w - 1,如果 i <= w,会出现负下标
            // 第 3 种情况,从小转移到大
            l = 1 , r = 0; // 单调队列准备
            for(int j = 0 ; j <= m ; j++){
                while(l <= r && q[l] < j - as)
                    l++; // 把过期的元素扔掉
                while(l <= r && f[i - w - 1][q[r]] + q[r] * ap <= f[i - w - 1][j] + j * ap)
                    r--; // 更新单调队列元素
                q[++r] = j; // 插入最新元素
                if(l <= r)
                    f[i][j] = Max(f[i][j] , f[i - w - 1][q[l]] + q[l] * ap - j * ap); 
                // 如果单调队列里有元素,即可转移
            }
            // 第 4 种情况,从大转移到小
            l = 1 , r = 0; // 单调队列再次准备
            for(int j = m ; j >= 0 ; j--){
                while(l <= r && q[l] > j + bs)
                    l++; // 把过期的元素扔掉
                while(l <= r && f[i - w - 1][q[r]] + q[r] * bp <= f[i - w - 1][j] + j * bp)
                    r--; // 更新单调队列元素
                q[++r] = j; // 插入最新元素
                if(l <= r)
                    f[i][j] = Max(f[i][j] , f[i - w - 1][q[l]] + q[l] * bp - j * bp); 
                // 如果单调队列里有元素,即可转移
            }
        }
        for(int i = 0 ; i <= m ; i++)
            ans = Max(ans , f[n][i]); // 最后,可以留股票(实际上不留任何股票的就是最优答案),找出最优答案
        printf("%d
    " , ans);
        return 0;
    }

     总结:

    • 从题面上看的话,两题最大的区别是数据范围和是否卖出这一条件
    • 从数据范围上我们可以推断出第一题使用三分,第二题使用线性DP
  • 相关阅读:
    unity 编辑器 对比两次节点信息 查看新增节点和消失节点。
    根据模型的Height进行颜色的渐变 (Shader相关)
    TimeLine一些思考
    (unity小工具)C# 获取选择的Gameobject对象被引用的类名和字段名
    copy节点相对prefab的路径 (unity小工具)
    使用LineRender绘制网格线
    龙书11_chapter_6 二:HillsDemo解析
    龙书11_chapter_6 一:一般绘制流程
    龙书11_chapter_4 三:每一小节关键点
    龙书11_chapter_4 二:习题中的Adapter
  • 原文地址:https://www.cnblogs.com/zbsy-wwx/p/11680645.html
Copyright © 2020-2023  润新知