欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第一章动态规划(十三)

程序员文章站 2022-07-13 11:30:01
...

单调队列优化的DP

例题:135. 最大子序和
第一章动态规划(十三)
单调队列
1、需要求某一个区间的连续子段和,因此需要用到前缀和s[ ]
2、对于任意的 s[k] ,求以 k 结尾的区间长度不超过 m 的最大连续和,等价于求 s[k] - s[k-j],其中 1<= j <= m,因为 a[k] 是固定的,所以即要求 s[k-j]的最小值即可。(单调队列存的是下标)

import java.util.Scanner;

public class Main {
    static int N = 300010;
    static int[] s = new int[N];
    static int hh = 0;
    static int tt = -1;
    static int[] q = new int[N];

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        for (int i = 1; i <= n; i++) {
            s[i] = scan.nextInt();
            s[i] += s[i - 1];
        }
        int res = -0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            if (q[hh] < i - m) hh++;
            res = Math.max(res, s[i] - s[q[hh]]);
            while (hh <= tt && s[q[tt]] >= s[i]) tt--;
            q[++tt] = i;
        }
        System.out.println(res);
    }
}

【小根堆实现】

import java.util.PriorityQueue;
import java.util.Scanner;

public class Main {
    static int N = 300010;
    static PriorityQueue<Pair> q = new PriorityQueue<Pair>((x, y) -> x.val - y.val);
    static int[] s = new int[N];

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int m = scan.nextInt();
        for (int i = 1; i <= n; i++) {
            s[i] = scan.nextInt();
            s[i] += s[i - 1];
        }

        int res = -0x3f3f3f3f;
        for (int i = 1; i <= n; i++) {
            int t = s[i] - s[i - 1];
            while (!q.isEmpty() && i - m > q.peek().idx) q.poll();
            if (!q.isEmpty()) t = Math.max(t, s[i] - q.peek().val);
            res = Math.max(res, t);
            q.add(new Pair(s[i], i));
        }
        System.out.println(res);
    }
}

class Pair {
    int val, idx;

    Pair(int val, int idx) {
        this.val = val;
        this.idx = idx;
    }
}

例题:旅行问题
【题目描述】
原题来自:POI 2004
John 打算驾驶一辆汽车周游一个环形公路。公路上总共有 n车站,每站都有若干升汽油(有的站可能油量为零),每升油可以让汽车行驶一千米。John 必须从某个车站出发,一直按顺时针(或逆时针)方向走遍所有的车站,并回到起点。在一开始的时候,汽车内油量为零,John 每到一个车站就把该站所有的油都带上(起点站亦是如此),行驶过程中不能出现没有油的情况。
任务:判断以每个车站为起点能否按条件成功周游一周。
【输入】
第一行是一个整数 n,表示环形公路上的车站数;
接下来 n行,每行两个整数 pi,di ,分别表示表示第 i 号车站的存油量和第 i号车站到下一站的距离。
【输出】
输出共 n行,如果从第 i 号车站出发,一直按顺时针(或逆时针)方向行驶,能够成功周游一圈,则在第 i 行输出 TAK,否则输出 NIE。
【输入样例】
5
3 1
1 2
5 2
0 1
5 4
【输出样例】
TAK
NIE
TAK
NIE
TAK
【提示】
数据范围与提示:
对于全部数据,3≤n≤106,0≤pi≤2×109,0<di≤2×109
————————————————————————————————————————————————————
【破环成链】
所有的环形问题都可以转为线性问题,将环拆成一条链,再复制一次加到链的后边,
比如环形的 5 个车站,编号 1—5,拆成链就是:
第一章动态规划(十三)
我们用数组存储每个车站的油量与距离的差值,这样判断从某个车站能否周游一圈就只需要判断从 1-----1 (也就是长度为 n 的区间内)的每个前缀和是否出现负数。也就是求前缀和数组,长度为 n 的区间最小值。
前缀和 s[ ]:从 1 出发
s[1] >= 0,说明可以到 2
s[2] < 0,说明不可以到 3
顺时针算一遍,将能够周游一圈的车站标记一下。
逆时针算一遍,将能够周游一圈的车站标记一下。

import java.util.Scanner;

public class Main {
    static int N = (int) (2e6 + 10);//破环成链需要两倍空间
    static int n;
    static int[] d = new int[N];
    static int[] o = new int[N];
    static long[] s = new long[N];
    static int[] q = new int[N];
    static boolean[] st = new boolean[N];//车站i是否能周游一圈

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            o[i] = sc.nextInt();
            d[i] = sc.nextInt();
        }
        //顺时针做
        for (int i = 1; i <= n; i++) {
            s[i] = o[i] - d[i];
            s[i + n] = o[i] - d[i];
        }
        for (int i = 1; i <= n * 2; i++) {
            s[i] += s[i - 1];
        }
        int hh = 0, tt = -1;
        for (int i = n * 2; i > 0; i--) {
            if (hh <= tt && q[hh] >= i + n) hh++;//删掉队头
            while (hh <= tt && s[q[tt]] >= s[i]) tt--;//包含s[i],先更新再求
            q[++tt] = i;
            if (i <= n) {
                if (s[q[hh]] >= s[i - 1]) st[i] = true;
            }
        }
        //逆时针做
        d[0] = d[n];
        for (int i = 1; i <= n; i++) {
            s[i] = o[i] - d[i - 1];
            s[i + n] = o[i] - d[i - 1];
        }
        for (int i = 1; i <= n * 2; i++) {
            s[i] += s[i - 1];
        }
        hh = 0;
        tt = -1;
        for (int i = 1; i <= n * 2; i++) {
            if (hh <= tt && q[hh] < i - n) hh++;
            if (i > n) {
                if (s[q[hh]] <= s[i]) st[i - n] = true;
            }
            while (hh <= tt && s[q[tt]] <= s[i]) tt--;//不包含s[i],先求再更新
            q[++tt] = i;
        }
        //输出答案
        for (int i = 1; i <= n; i++) {
            if (st[i]) {
                System.out.println("TAK");
            }else {
                System.out.println("NIE");
            }
        }
    }
}

例题:烽火传递
【题目描述】
原题来自:NOIP 2010 提高组初赛 · 完善程序
烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发生,则白天用浓烟,晚上有火光传递军情。
在某两个城市之间有 n座烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确传递,在连续 m 个烽火台中至少要有一个发出信号。现在输入 n,m和每个烽火台的代价,请计算总共最少的代价在两城市之间来准确传递情报。
【输入】
第一行是 n,m,表示 n 个烽火台和连续烽火台数 m;
第二行 n个整数表示每个烽火台的代价 ai。
【输出】
输出仅一个整数,表示最小代价。
【输入样例】
5 3
1 2 5 6 2
【输出样例】
4
【提示】
样例说明
在第 2,5号烽火台上发信号。
数据范围与提示:
对于全部数据,1≤n,m≤2×105,1≤ai≤1000。
——————————————————————————————————————————————————
第一章动态规划(十三)

import java.util.Scanner;

public class Main {
    static int N = 200010;
    static int n;
    static int m;
    static int[] w = new int[N];
    static int[] f = new int[N];
    static int[] q = new int[N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            w[i] = sc.nextInt();
        }
        int hh = 0, tt = 0;
        for (int i = 1; i <= n; i++) {
            if (q[hh] < i - m) hh++;
            f[i] = f[q[hh]] + w[i];
            while (hh <= tt && f[q[tt]] >= f[i]) tt--;
            q[++tt] = i;
        }
        int res = Integer.MAX_VALUE >> 1;
        for (int i = n - m + 1; i <= n; i++) {
            res = Math.min(res, f[i]);
        }
        System.out.println(res);
    }
}

例题:绿色通道
【题目描述】
高二数学《绿色通道》总共有 n道题目要抄,编号 1…n,抄第 i 题要花 ai 分钟。小 Y 决定只用不超过 t分钟抄这个,因此必然有空着的题。每道题要么不写,要么抄完,不能写一半。下标连续的一些空题称为一个空题段,它的长度就是所包含的题目数。这样应付自然会引起马老师的愤怒,最长的空题段越长,马老师越生气。
现在,小 Y 想知道他在这 t分钟内写哪些题,才能够尽量减轻马老师的怒火。由于小 Y 很聪明,你只要告诉他最长的空题段至少有多长就可以了,不需输出方案。
【输入】
第一行为两个整数 n,t。
第二行为 n个整数,依次为 a1,a2,…,an。
【输出】
输出一个整数,表示最长的空题段至少有多长。
【输入样例】
17 11
6 4 5 2 5 3 4 5 2 3 4 5 2 3 6 3 5
【输出样例】
3
【提示】
样例说明:
第一章动态规划(十三)
数据范围与提示:
对于 60% 数据,n≤2000;
对于所有数据,0<n≤5×104,0<ai≤3000,0<t≤108
————————————————————————————————————————————————————

import java.util.Scanner;

public class Main {
    static int N = 50010;
    static int n;
    static int m;
    static int[] w = new int[N];
    static int[] f = new int[N];
    static int[] q = new int[N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            w[i] = sc.nextInt();
        }
        int l = 0, r = n;
        while (l < r) {
            int mid = l + r >> 1;
            if (check(mid)) {
                r = mid;
            } else {
                l = mid + 1;
            }
        }
        System.out.println(l);
    }

    private static boolean check(int limit) {
        int hh = 0, tt = 0;
        for (int i = 1; i <= n; i++) {
            if (q[hh] < i - limit - 1) hh++;
            f[i] = f[q[hh]] + w[i];
            while (hh <= tt && f[q[tt]] >= f[i]) tt--;
            q[++tt] = i;
        }
        for (int i = n - limit; i <= n; i++) {
            if (f[i] <= m) return true;
        }
        return false;
    }
}

例题:修剪草坪
【题目描述】
原题来自:USACO 2011 Open Gold
在一年前赢得了小镇的最佳草坪比赛后,FJ 变得很懒,再也没有修剪过草坪。现在,新一轮的最佳草坪比赛又开始了,FJ 希望能够再次夺冠。
然而,FJ 的草坪非常脏乱,因此,FJ 只能够让他的奶牛来完成这项工作。FJ 有 N只排成一排的奶牛,编号为 1 到 N。每只奶牛的效率是不同的,奶牛 i 的效率为 Ei。
靠近的奶牛们很熟悉,如果 FJ 安排超过 K只连续的奶牛,那么这些奶牛就会罢工去开派对。因此,现在 FJ 需要你的帮助,计算 FJ 可以得到的最大效率,并且该方案中没有连续的超过 K只奶牛。
【输入】
第一行:空格隔开的两个整数 N和 K;
第二到 N+1行:第 i+1 行有一个整数 Ei。
【输出】
一行一个值,表示 FJ 可以得到的最大的效率值。
【输入样例】
5 2
1
2
3
4
5
【输出样例】
12
【提示】
样例说明:
FJ 有 5只奶牛,他们的效率为 1,2,3,4,5。他们希望选取效率总和最大的奶牛,但是他不能选取超过 2 只连续的奶牛。FJ 选择出了第三只以外的其他奶牛,总的效率为 1+2+4+5=12。
数据范围与提示:
对于全部数据,1≤N≤105,0≤Ei≤109
——————————————————————————————————————————————————
第一章动态规划(十三)
数组越界了…感觉和y总代码一样为什么他的C++代码没越界…

import java.util.Scanner;

public class Main {
    static int N = (int) (1e5 + 10);
    static int n;
    static int m;
    static long[] s = new long[N];
    static long[] f = new long[N];
    static int[] q = new int[N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            s[i] = sc.nextLong();
            s[i] += s[i - 1];
        }
        int hh = 0, tt = 0;
        for (int i = 1; i <= n; i++) {
            if (q[hh] < i - m) hh++;
            f[i] = Math.max(f[i - 1], g(q[hh]) + s[i]);
            while (hh <= tt && g(q[tt]) <= g(i)) tt--;
            q[++tt] = i;
        }
        System.out.println(f[n]);
    }

    private static long g(int i) {
        return f[i - 1] - s[i];
    }
}

例题:理想的正方形
【题目描述】
原题来自:HAOI 2007
有一个 a×b的整数组成的矩阵,现请你从中找出一个 n×n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
【输入】
第一行为三个整数,分别表示 a,b,n的值;
第二行至第 a+1行每行为 b个非负整数,表示矩阵中相应位置上的数。
【输出】
输出仅一个整数,为 a×b矩阵中所有「n×n正方形区域中的最大整数和最小整数的差值」的最小值。
【输入样例】
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
【输出样例】
1
【提示】
数据范围与提示:
对于 20% 的数据 2≤a,b≤100,n≤10;
对于 100% 的数据 2≤a,b≤1000,n≤a,n≤b,n≤100,矩阵中的所有数都不超过 109
————————————————————————————————————————————————————
有错…

import java.util.Scanner;

public class Main {
    static int N = 1010;
    static int n;
    static int m;
    static int k;
    static int[][] w = new int[N][N];
    static int[][] row_max = new int[N][N];
    static int[][] row_min = new int[N][N];
    static int[] q = new int[N];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        k = sc.nextInt();
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                w[i][j] = sc.nextInt();
            }
        }
        for (int i = 1; i <= n; i++) {
            get_min(w[i], row_min[i], m);
            get_max(w[i], row_min[i], m);
            
        }
        int res = Integer.MAX_VALUE >> 1;
        int[] a = new int[N];
        int[] b = new int[N];
        int[] c = new int[N];
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                a[j] = row_min[j][i];
            }
            get_min(a, b, n);
            for (int j = 1; j <= n; j++) {
                a[j] = row_max[j][i];
            }
            get_max(a, c, n);

            for (int j = k; j <= n; j++) {
                res = Math.min(res, c[j] - b[j]);
            }
        }
        System.out.println(res);
    }

    private static void get_max(int[] a, int[] b, int tot) {
        int hh = 0, tt = -1;
        for (int i = 1; i <= tot; i++) {
            if (hh <= tt && q[hh] <= i - k) hh++;
            while (hh <= tt && a[q[tt]] <= a[i]) tt--;
            q[++tt] = i;
            b[i] = a[q[hh]];
        }
    }

    private static void get_min(int[] a, int[] b, int tot) {
        int hh = 0, tt = -1;
        for (int i = 1; i <= tot; i++) {
            if (hh <= tt && q[hh] <= i - k) hh++;
            while (hh <= tt && a[q[tt]] >= a[i]) tt--;
            q[++tt] = i;
            b[i] = a[q[hh]];
        }
    }
}
相关标签: 算法提高课