dp优化----单调队列解决定长连续区间最小值。

目录

例题一:Codeforces Round 466 (Div. 2) Cashback

题目大意:

思路解析:

代码实现:

例题二:poj2373 dividing the path

题目大意:

思路解析:

代码实现:

例题三:poj3017 cut the sequence

题目大意:

思路解析:

代码实现:

例题四:瑰丽华尔兹

思路解析:

代码实现:

例题5:多重背包使用单调队列优化实现

思路解析:

代码实现:


例题一:Codeforces Round 466 (Div. 2) Cashback

题目大意:

思路解析:

如果c==1,那么无论如何 答案都为0.
如果c!=1,我们考虑如果最优答案有一段区间长度小于c,那么它对答案的贡献值,等于将这个区间划分为长度为1的多个小段,

如果有一段区间长度大于c小于2 * c,那么相当于在里面找到需要删除的数在其旁划分出一个长度为c的区间,其余部分都划分长度为1的小区间。

如果有区间长度等于2*c,那么把c划分为两个长度为c的区间,只能一个区间删除一个对于这个区间长度为2*c才是等价的(其余情况,划分为单个长度为c的区间对于整体答案更加优秀)。说明无论如何将整体划分长度为1或者长度为n的区间是最优选择。

代码实现:

import java.io.*;
import java.math.BigInteger;
import java.util.*;


public class Main {

    public static void main(String[] args) throws IOException {
        int n = input.nextInt();
        int c = input.nextInt();
        int[] arr = new int[n + 1];
        int[] min = new int[n + 1];
        long[] dp = new long[n + 1];
        long[] sum = new long[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = input.nextInt();
            sum[i] = sum[i - 1] + arr[i];
        }
        LinkedList<Integer> list = new LinkedList<>();
        for (int i = 1; i <= Math.min(c, n); i++) {
            if (list.isEmpty()){
                list.add(i);
            }else{
                int j = list.getLast();
                if(arr[j] <= arr[i]){
                    list.add(i);
                }else{
                    while (arr[list.getLast()] > arr[i]){
                        list.removeLast();
                        if (list.isEmpty()) break;
                    }
                    list.add(i);
                }
            }
            min[i] = arr[list.getFirst()];
        }
        for (int i = c + 1; i <= n; i++) {
            while (!list.isEmpty() && list.getFirst() <= i - c) list.remove();
            if (list.isEmpty()){
                list.add(i);
            }else{
                int j = list.getLast();
                if(arr[j] <= arr[i]){
                    list.add(i);
                }else{
                    while (arr[list.getLast()] > arr[i]){
                        list.removeLast();
                        if (list.isEmpty()) break;
                    }
                    list.add(i);
                }
            }
            min[i] = arr[list.getFirst()];
        }
        for (int i = 1; i <= n; i++) {
            if (i >= c)
                dp[i] = Math.min(dp[i - 1] + arr[i], dp[i - c] + sum[i] - sum[i - c] - min[i]);
            else dp[i] = dp[i - 1] + arr[i];
        }
        out.println(dp[n]);
        out.flush();
        out.close();
        br.close();
    }



    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static Input input = new Input(System.in);
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    static class Input {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public Input(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public String nextLine() {
            String str = null;
            try {
                str = reader.readLine();
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
            return str;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

        public Double nextDouble() {
            return Double.parseDouble(next());
        }

        public BigInteger nextBigInteger() {
            return new BigInteger(next());
        }
    }

}

例题二:poj2373 dividing the path

题目大意:

现在有一个长度为L的牧场,有N头牛,每个牛有一个活动范围(s,e)  (为开区间),牛的活动范围可以重叠。现在有一个喷头,它喷射范围为【a,b】,需要使用这种喷头给牧场所有区域进行喷水,要求喷射范围不能超过牧场的边界。每个牛的活动范围只能被一个喷头喷射,且每个喷头的喷射范围不能重叠,求使得满足这些要求最少需要几个喷头。

思路解析:

因为喷射半径为整数且一个区域不能重复喷射,那么直径一定为偶数,即只能满足偶数的区间。

用dp[i]表示当前已经喷射了1-i的区域最少需要使用几个喷头,那么如果 i 落在牛的活动范围,这就是违法状态,应该赋inf。如何线性的判断,i是否落在牛的活动范围就是一个关键。

假设牛的活动范围 (2,5) (3,7)

那么让 sum[3]++,sum[5]--,sum[4]++,sum[7]--,再做一个前缀和,其中前缀和大于0的位置位于牛的活动范围内。

dp[i] = max(dp[j] + 1) i - 2 *b<= j<=i - 2*a,满足单调性,可以使用单调队列维护当前定长区间的最小值。

代码实现:

import java.io.*;
import java.math.BigInteger;
import java.util.*;


public class Main {

    public static void main(String[] args) throws IOException {
        int n = input.nextInt();
        int l = input.nextInt();
        int a = input.nextInt();
        int b = input.nextInt();
        int inf = 1 << 31;
        int[] cowTree = new int[l + 1];
        for (int i = 0; i < n; i++) {
            int x= input.nextInt();
            int y = input.nextInt();
            cowTree[x + 1]++;
            cowTree[y]--;
        }
        int[] dp = new int[l + 1];
        int sum = 0;
        for (int i = 0; i <= l; i++) {
            dp[i] = inf;
            sum += cowTree[i];
            cowTree[i] = sum > 0 ? 1 : 0;
        }
        a <<= 1;
        b <<= 1;
        LinkedList<Integer> list = new LinkedList<>();
        for (int i = a; i <= b; i++) {
            if (cowTree[i] == 0){
                dp[i] = 1;
                if (i <= b + 2 - a){
                    list.add(i);
                }
            }
        }
        for (int i = b + 2; i <= l; i+=2) {
            if (cowTree[i] == 0){
                while (!list.isEmpty()){
                    if (list.getFirst() < i - b) list.remove();
                    else break;
                }
                if(!list.isEmpty())
                    dp[i] = dp[list.getFirst()] + 1;
            }
            if (dp[i - a + 2] != inf) list.add(i - a + 2);

        }
        if (dp[l] == inf) out.println(-1);
        else out.println(dp[l]);
        out.flush();
        out.close();
        br.close();
    }



    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static Input input = new Input(System.in);
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    static class Input {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public Input(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public String nextLine() {
            String str = null;
            try {
                str = reader.readLine();
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
            return str;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

        public Double nextDouble() {
            return Double.parseDouble(next());
        }

        public BigInteger nextBigInteger() {
            return new BigInteger(next());
        }
    }

}

例题三:poj3017 cut the sequence

题目大意:

给定一个长度为n序列,求一种分割方式,使得每一个部分的和都满足不大于m的情况下求每个部分的最大值的和最小。

0<=N<=100000

A[i] >=0, M <= 4^18

思路解析:

dp[i]表示前i个序列,分割后每个部分的最大值的和最小是多少。

如果下标 j, j+1,j+2......i这多个数中最大数下标为k,这多个数之和小于m,那么其实可以发现dp[j-1]+arr[k]<=dp[j]+arr[k]<=dp[j+1]+arr[k],这是一定成立的条件。那么我们应该优先选择最小区间来进行合并答案。这又要求区间的最大值,又可以实现单调队列来实现,但是队首的最大值不一定是最优秀的划分答案,因为可以选择将队首这个最大值划分到上一个区间,所以要经历一个遍历的过程。

代码实现:

import java.io.*;
import java.math.BigInteger;
import java.util.*;


public class Main {

    public static void main(String[] args) throws IOException {
        int n = input.nextInt();
        long m = input.nextLong();
        long[] arr = new long[n + 1];
        boolean flag = false;
        for (int i = 1; i <= n; i++) {
            arr[i] = input.nextLong();
            if (arr[i] > m) flag = true;
        }
        if (flag) {
            out.println(-1);
        } else {
            int p = 1;
            long sum = 0;
            long[] dp = new long[n + 1];
            int[] q = new int[n + 1]; // 用数组实现单调队列,方便dp时循环。
            int l = 0; // [l,r)代表当前单调队列数组有效范围
            int r = 0;
            q[r++] = 0;
            dp[1] = arr[1]; // dp[i]表示前i个数经分割后,每个部分最大值的和最小是多少。
            for (int i = 1; i <= n; i++) {
                sum += arr[i];
                while (sum > m) sum -= arr[p++];
                
                // 实现单调队列
                while (l < r && q[l] < p) l++; 
                while (l < r && arr[q[r - 1]] <= arr[i]) r--;
                q[r++] = i;
                
                dp[i] = dp[p - 1] + arr[q[l]]; 
                for (int j = l; j < r - 1; j++) {
                    dp[i] = Math.min(dp[i], dp[q[l]] + arr[q[l + 1]]);
                }
            }
            out.println(dp[n]);
        }
        out.flush();
        out.close();
        br.close();
    }


    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static Input input = new Input(System.in);
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    static class Input {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public Input(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public String nextLine() {
            String str = null;
            try {
                str = reader.readLine();
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
            return str;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

        public Double nextDouble() {
            return Double.parseDouble(next());
        }

        public BigInteger nextBigInteger() {
            return new BigInteger(next());
        }
    }

}

例题四:瑰丽华尔兹

思路解析:

每一个时间段,它只会往一个方向滑动,所以我们单独考虑每一个时间的滑行情况,这样就只需要对一个方向进行统计。

dp[i][j][k] 表示当前第k个时间段已经滑动了地图的(i,j)处最多能滑动多少时间。

假如当前向下滑动,dp[i][j][k] = max(dp[i'][j][k-1] + i - i') 等价于 max(dp[i'][j][k-1] - i') + i。那么又是定长区间求最大值,又可以使用单调队列进行优化。 // 可以利用就地滚动的特点来优化dp的空间消耗。

代码实现:

import java.io.*;
import java.math.BigInteger;
import java.util.*;


public class Main {
    static int[] dx = {0, -1, 1, 0, 0};
    static int[] dy = {0, 0, 0, -1, 1};
    static int ans = 0;
    static int[] q = new int[205];
    static int[] p = new int[205];
    static int N;
    static int M;
    static int[][] map;
    static int[][] dp;

    public static void main(String[] args) throws IOException {
        N = input.nextInt();
        M = input.nextInt();
        int x = input.nextInt();
        int y = input.nextInt();
        int k = input.nextInt();
        int inf = Integer.MIN_VALUE;
        map = new int[N + 1][M + 1];
        for (int i = 0; i < N; i++) {
            String str = input.next();
            char[] s = str.toCharArray();
            for (int j = 0; j < s.length; j++) {
                if (s[j] == 'x') map[i + 1][j + 1] = 1; // 表示当前地方有家具
            }
        }
        dp = new int[N + 1][M + 1];
        for (int i = 0; i <= N; i++) {
            Arrays.fill(dp[i], inf);
        }
        dp[x][y] = 0;
        for (int l = 1; l <= k; l++) {
            int st = input.nextInt();
            int et = input.nextInt();
            int d = input.nextInt();
            int len = et - st + 1; // 当前时间段最多能滑动多远
            if (d == 1) { // 向上滑动
                for (int i = 1; i <= M; i++) {
                    work(N, i, len, d);
                }
            } else if (d == 2) { // 向下滑动
                for (int i = 1; i <= M; i++) {
                    work(1, i, len, d);
                }
            } else if (d == 3) { // 向左滑动
                for (int i = 1; i <= N; i++) {
                    work(i, M, len, d);
                }
            } else if (d == 4) { // 向右滑动
                for (int i = 1; i <= N; i++) {
                    work(i, 1, len, d);
                }
            }
        }
        out.println(ans);
        out.flush();
        out.close();
        br.close();
    }

    public static void work(int x, int y, int len, int d) {
        int head = 1;
        int tail = 0;
        for (int i = 1; x >= 1 && x <= N && y >= 1 && y <= M; i++, x += dx[d], y += dy[d]) {
            if (map[x][y] == 1) { // 如果当前位是家具,应该清空队列
                head = 1;
                tail = 0;
            } else {
                while (head <= tail && dp[x][y] >= q[tail] + i - p[tail]) tail--; // 保存队列的单调性
                q[++tail] = dp[x][y]; // 保存改变之前的值,将其加入队列
                p[tail] = i;
                if (p[tail] - p[head] > len) head++;
                dp[x][y] = q[head] + i - p[head]; // 用队列的最优值进行转移
                ans = Math.max(dp[x][y], ans);
            }
        }
    }

    static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    static Input input = new Input(System.in);
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    static class Input {
        public BufferedReader reader;
        public StringTokenizer tokenizer;

        public Input(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            tokenizer = null;
        }

        public String next() {
            while (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public String nextLine() {
            String str = null;
            try {
                str = reader.readLine();
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }
            return str;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

        public Double nextDouble() {
            return Double.parseDouble(next());
        }

        public BigInteger nextBigInteger() {
            return new BigInteger(next());
        }
    }

}

例题5:多重背包使用单调队列优化实现

给定n种物品和一个背包,第i种物品的体积为ci,价值为wi,并且有mi个,背包的总容量为C,如何选择装入背包的物品,使背包装入的物品的总价值最大。

思路解析:

dp[i] = max(dp[j] + k*wi) 等价于 max(dp[i - k*c] + k*wi) 等价于 max(dp[b+y*c] - y*w) + m * w

b+m*c = i, 即假设用这个物品将背包剩余部分全部装完,然后枚举有y个物品不装,然后求这里面的最大值,这又可以变为单调队列来优化。

代码实现:

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, C;
int dp[N], q[N], num[N];
int w,c, m;
int main(){
    cin >> n >> C;
    memset(dp, 0, sizeof(dp));
    for(int i=1;i<=n;i++){
        cin >> m >> c >> w;
        if (m > C / c) m = C/c;
        for(int b = 0; b < c; b++){
            int head = 1, tail = 1;
            for(int y = 0; y <= (C - b) / c; y++){
                int tmp = dp[b + y * c] - y*w;
                while(head < tail && tmp >= q[tail - 1]) tail--;
                q[tail] = tmp;
                num[tail++]=y;
                while (head < tail && y - num[head] > m) head++;
                dp[b + y * c] = max(dp[b+  y*c], q[head] + y*w);
            }
        }
    }
    cout<<dp[C] <<endl;
}

  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Studying~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值