zoukankan      html  css  js  c++  java
  • 洛谷网校:动态规划(二)单调队列优化 p1725,p3572

    2020.2.3

    动态规划(二)单调队列优化

    p1725 琪露诺

    题目描述

    小河可以看作一列格子依次编号为0到N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子i时,她只移动到区间[i+l,i+r]中的任意一格。

    每一个格子都有一个冰冻指数A[i],编号为0的格子冰冻指数为0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数A[i]。琪露诺希望能够在到达对岸时,获取最大的冰冻指数。

    开始时,琪露诺在编号0的格子上,只要她下一步的位置编号大于N就算到达对岸。

    输入格式

    第1行:3个正整数N, L, R

    第2行:N+1个整数,第i个数表示编号为i-1的格子的冰冻指数A[i-1]

    输出格式

    一个整数,表示最大冰冻指数。保证不超过2^31^-1

    题解(批注加粗)

    f~i~表示从起点走到第i个格子的最大收益。

    f~i~=max(f~j~+a~i~) (i−r≤j≤i−l)。由j的状态转移到i的状态(递推)

    在当前决定的节点之前(用窗口更新当前而不是用当前更新窗口)维护一个滑动窗口,每次求窗口中f~i~的最大值。

    用一个单调队列维护窗口里所有可能用到的决策点。

    对于两个点x,y,如果x<y且f~x~≤ f~y~,那么y进入窗口后,决策点一定不是x。由于是从前往后递推,若x<y(y比x靠后),那么当前y可以更新格子的一定比x多,又因为由y更新得到的答案不小于x,所以x在此时此刻就没用了

    如果一个节点y入队,则无需条件,但是入队前要弹出队列右端所有符合上文条件的x

    在一番弹出后,就能保证队列中的f~i~从队首到队尾递减。

    决策时,首先弹掉队首超过范围的点(<i-R)

    这时队首点就是决策点。最左边头一个点,因为递减,所以最左边的值最大

    依然是老师代码(简化~实测AC~)批注

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #define inf 0x3f3f3f3f
    #define N 200005
    using namespace std;
    const int INF=0x7f7f7f7f;
    int n,L,R,a[N]/*题目输入的四个变量*/,f[N]/*dp数组*/,q[N]/*队列*/,l,r/*滑动窗口范围*/;
    int main(){
    	cin>>n>>L>>R;//第一行
    	for(int i=0;i<=n;i++) {
            scanf("%d",&a[i]);//冰冻指数
        }
    	f[0]=a[0];//边界:一开始就在第0个格子,无需移动
    	l=0;
        r=-1;
    	for(int i=1;i<=n;i++) {
    		while(l<=r&&q[l]<i-R) {//q[l]存的格子已经超出i之前R的范围,且队列不为空,可以弹出(因为已经无法对i造成影响)
                l++;//弹出q[l]
            }
    		if(i-L>=0) {//保证当前操作的格子编号为非负数
    			while(l<=r&&f[i-L]>=f[q[r]]) {//保证队列不空且加入新格子[i-L]后,r不可以更新答案,i-L可以完全替代或者比r更优,弹出所有这样的r
                    r--;
                }
    			q[++r]=i-L;//加入i-L
    		}
    		if(l>r) {//队空
                f[i]=-INF;//没有到i的方案
            }
    		else {
                f[i]=f[q[l]]+a[i];//队列有元素,用队列最左边的元素更新f[i](不用担心队中其他元素,是金子总会发光,如果某元素有用,一定会留下来,成为下一个l,如果l一直能更新新的格子,那么l会一直待到l<i-R被迫老死(弹出)的那一刻)
            }
    	}//到这里,递推循环结束
    	int ans=f[n];//开始筛选最大值为答案
    	for(int i=n-R+1;i<=n;i++) {
            ans=max(ans,f[i]);//只要“大于”n就算到达对岸,所以最远可能从n-R+1直接跳R格到n+1;最近是从n跳任意k步都能到n+k,又因为n以后的位置没有收益,所以答案应该在n-R+1~n中找
        }
    	cout<<ans;
    	return 0;
    }
    

    p3572 PTA-Little Bird

    题目描述

    n棵树排成一行,第i棵树高度h~i~。一只鸟想从1跳到n。当它在第i棵树上时,它可以一步跳到 [i+1,i+K]中的任意一棵树。 它从较高的树向较矮的树跳时不消耗体力,否则消耗 1 的体力。 询问 Q 次,每次给定 K,求最小消耗体力。

    从1开始,跳到比当前矮的不消耗体力,否则消耗一点体力,每次询问有一个步伐限制,求每次最少耗费多少体力

    输入格式(简化)(自己翻译成汉语又翻译回英语)

    There is a single integer n (2≤n≤1 000 000)

    The second line holds n int d~1~~d~n~ is the height of the trees.

    The third line Q (1≤q≤25)

    The following Q lines: k~i~(1≤ki≤n−1)

    输出格式(简化)

    Print q lines,The i~th~ line(the i~th~ bird energy)

    题解(批注加粗)

    f(i)表示跳到第i棵树消耗的最少体力。dp数组

    状态转移方程:f(i)=min(f(j)+(h~j~<h~i~)) (i−K≤j≤i−1)。

    如果h~i~(目标树)大那体力就加一,h~j~(起点树)大体力就不变

    设x<y,若f(x)>f(y),(无论i的高矮都是f(y)小,和上题类似,y明显比较优)或 f(x)=f(y) 且 h~x~ ≤ h~y~,(这时,如果转移到x,y都能得到一样的f,但是对以后的转移,如果从x到下一棵树消耗体力,那么y到同一棵树不一定消耗体力,y优于x),那么选y不会变差,x没用。这时弹出x

    结论队尾为x,新点为 y,如果 f(x) > f(y),或f(x)=f(y)且h~x~≤h~y~, 把 x 弹掉。

    所以,是靠队列状态转移到当前状态,和上一题一样

    复杂度O(Qn)

    老师的代码(简化~实测AC~)+批注

    #include <iostream>
    #include <cstdio>
    #define N 1000010
    using namespace std;
    int n,Q/*题目给出的n,Q*/,K/*当前鸟的k值,避免浪费数组*/,a[N]/*树高*/,f[N]/*DP*/,q[N]/*队列*/,l,r/*队列左右界*/;
    int main() {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
        }
        scanf("%d",&Q);
        while(Q--){
            scanf("%d",&K);//本行以上都是读入
            q[0]=1;//第一棵树已经在队内
            l=0;
            r=0;//一开始的队列只有元素0
            for(int i=2;i<=n;i++) {
                while(i-q[l]>K) {//弹出队首的条件:老死(i,l距离已经超过k,鸟跳不过去)
                    l++;
                }
                f[i]=f[q[l]]+(a[q[l]]<=a[i]);//同样是用队首更新当前,只要高度上升就使体力加一
                while(l<=r&&(f[i]<f[q[r]]||(f[i]==f[q[r]]&&a[i]>a[q[r]]))) {//条件说明:(队列不空&&(队尾体力大于当前体力||(当前体力和队尾体力相等&&当前高度大于队尾))):这里讨论是否弹出队尾,队列不空是前提,分两种情况符合弹出条件,一、队尾体力消耗已经比当前大了,不可能更新当前。二、队尾体力虽然和当前相等,但是因高度问题,只要到了当前树就会体力+1,也不能更新当前
                    r--;//弹出
                }//一通弹出过后,可以保证队列单调递增
                q[++r]=i;//i入队,准备更新下一棵树
            }
            printf("%d
    ",f[n]);//f[n]就是答案
        }
        return 0;
    }
    
  • 相关阅读:
    《设备树 — 引入设备树,内核文件的位置变化(八)》
    《设备树 — 内核中设备树的操作函数(七)》
    《设备树 — 根文件系统中查看设备树(六)》
    《设备树 — platform_device和platform_driver如何让匹配(五)》
    《设备树 — device node到platform device的转换(四)》
    Ex 6_19 至多用k枚硬币兑换价格_第七次作业
    Ex 6_18 硬币有限的兑换问题_第七次作业
    Ex 6_17 数量无限的硬币兑换问题_第七次作业
    Ex 6_16 旧货销售问题_第七次作业
    Ex 6_21 最小点覆盖问题_第八次作业
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/12260881.html
Copyright © 2011-2022 走看看