zoukankan      html  css  js  c++  java
  • 一道贪心:加括号使算式的值最大

    问题描述

    给定一个算术表达式形如1+3-5-4+6,表达式中的运算数全部都是正数,运算符全部是加号或者减号。
    现在可以给算术表达式加任意多的括号,使得表达式的值最大。
    如对于1+3-6-9+4-5-7+8,可以1+3-(6-9)+4-(5-7)+8,最优的方案是1+3-(6-(9+4)-5-(7+8))

    数据格式:
    输入:第一行输入一个n表示运算数的个数,第二行输入一个表达式。
    输出:一个数字,表示表达式的最大值。
    数据范围:运算数个数为1e5。

    解析

    最优答案不唯一,我们只需要考虑如何构造出最优答案。

    • 最优答案中,左括号只加在减号后面,因为加在加号后面跟不加没有区别
    • 最优答案中,右括号不能加在若干个加号中间,例如3-4+5+6+7-8,其中5,6,7之间肯定没有括号。可以用移动括号的办法来解释:左括号不能加在加号后面(第一条已证),右括号如果加在若干个加号之间,总可以通过移动右括号来使算式更大,也就是说,右括号放在加号之间时不稳定的。

    先看几个例题:
    1-2-3 => 1-(2-3) 通过加括号可以是减号变成加号
    1-2+3-5 => 1-(2+3-5)
    1-2+3-2 => 1-(2)+3-(2) 对于a-b+c-d的形式,如果d>c,那么需要把d括进来,否则在c前面放上右括号
    1-2+3-2+5-100 =>1-(2+3-(2+5)-100) 对上面那条规则进行修正,此例中虽然2<3,但是后面还有个5

    分两类情况:

    • 如果表达式形如a-b-c...,那么a-(b-c...)括号中的全部符号除了b都能够取到绝对值
    • 如果表达式形如a-b+c...,那么需要分两类行情况讨论。如果一括到底,可得收益x;如果及时中断,可以递归解决子问题,得到收益y。因为问题规模较大,递归有可能栈溢出,所以可以使用动态规划倒序处理。

    最优答案中,括号肯定不会嵌套超过两层:这是因为减号两次嵌套相当于没有嵌套。
    贪心的原则就是,既然无论如何都要给右面留下左括号,那么左括号的位置必然是最优的。

    此算法复杂度O(n)

    还有一种更加直观的O(n^3)算法,max(f,t)和min(f,t)表示[f,t]闭区间上的最大值和最小值,也是动态规划。

    下面的代码使用两种算法实现,经过两种算法互相验证,基本可以断言两种算法都是正确的。

    import java.util.Arrays;
    import java.util.Random;
    
    public class Main {
    int[] a;
    char[] op;
    Random random = new Random(0);
    
    void generateProblem() {
        int n = random.nextInt(60) + 1;
        a = new int[n];
        op = new char[n];
        for (int i = 0; i < n; i++) {
            a[i] = random.nextInt(200);
            if (i == 0) op[i] = '+';
            else {
                op[i] = random.nextBoolean() ? '+' : '-';
            }
        }
    }
    
    //暴力方法求正确答案
    class Bruteforce {
        int min[][] = new int[a.length][a.length];
        int max[][] = new int[a.length][a.length];
    
        int solve() {
            for (int i = 0; i < a.length; i++) {
                min[i][i] = a[i];
                max[i][i] = a[i];
            }
            for (int step = 1; step < a.length; step++) {
                for (int i = 0; i + step < a.length; i++) {
                    int f = i, t = i + step;
                    int s = a[f];
                    for (int j = f + 1; j <= t; j++) {
                        s += (op[j] == '+' ? 1 : -1) * a[j];
                    }
                    min[f][t] = max[f][t] = s;
                    for (int j = f + 1; j <= t; j++) {
                        if (op[j] == '-') {
                            int nowMax = max[f][j - 1] - min[j][t];
                            int nowMin = min[f][j - 1] - max[j][t];
                            min[f][t] = Math.min(min[f][t], nowMin);
                            max[f][t] = Math.max(max[f][t], nowMax);
                        }
                    }
                }
            }
            return max[0][a.length - 1];
        }
    }
    
    //最好的方法
    class Best {
        int[] a;
        char[] op;
    
        int max[];//max[i]表示从i到末尾表达式的最大值
        int suffix[];//min[i]表示从i到末尾表达式的最小值
    
        //压缩正号
        void compress() {
            int i = 0;
            a = Arrays.copyOf(Main.this.a, Main.this.a.length);
            op = Arrays.copyOf(Main.this.op, Main.this.op.length);
            for (int j = 1; j < a.length; j++) {
                if (op[i] == '+' && op[j] == '+') {
                    a[i] += a[j];
                } else {
                    i++;
                    op[i] = op[j];
                    a[i] = a[j];
                }
            }
            a = Arrays.copyOf(a, i + 1);
            op = Arrays.copyOf(op, i + 1);
        }
    
    
        int solve() {
            compress();
            max = new int[a.length];
            suffix = new int[a.length];
            suffix[suffix.length - 1] = a[suffix.length - 1];
            for (int i = suffix.length - 2; i >= 0; i--) {
                suffix[i] = a[i] + suffix[i + 1];
            }
            max[a.length - 1] = a[a.length - 1];
            for (int i = a.length - 2; i >= 0; i--) {
                if (op[i + 1] == '+') {
                    max[i] = max[i + 1] + a[i];
                } else {
                    if (i + 2 >= a.length) {
                        max[i] = a[i] - a[i + 1];
                        continue;
                    }
                    if (op[i + 2] == '-') {//连续两个减号
                        max[i] = a[i] - a[i + 1] + suffix[i + 2];
                    } else {//-+-
                        //一括到底
                        int s = a[i] - a[i + 1] - a[i + 2] + (i + 3 < suffix.length ? suffix[i + 3] : 0);
                        //只扩一个
                        int ss = a[i] - a[i + 1] + max[i + 2];
                        max[i] = Math.max(s, ss);
                    }
                }
            }
            return max[0];
        }
    }
    
    String tos() {
        StringBuilder builder = new StringBuilder();
        builder.append(a[0]);
        for (int j = 1; j < a.length; j++) {
            builder.append(op[j]).append(a[j]);
        }
        return builder.toString();
    }
    
    Main() {
        for (int i = 0; i < 10000; i++) {
            generateProblem();
            System.out.println(tos());
            int ans = new Bruteforce().solve(), mine = new Best().solve();
            System.out.println("ans:" + ans + " mine:" + mine);
            if (ans != mine) {
                throw new RuntimeException("error");
            }
        }
    }
    
    public static void main(String[] args) {
        new Main();
    }
    }
    
  • 相关阅读:
    LintCode Update Bits
    LintCode Flip Bits
    LintCode Wood Cut
    LintCode Sqrt(x)
    LintCode Binary Search
    LintCode Subarray Sum Closest
    Huffman编码(Huffman树)
    DFS应用——查找强分支
    DFS应用——遍历有向图+判断有向图是否有圈
    DFS应用——找出无向图的割点
  • 原文地址:https://www.cnblogs.com/weiyinfu/p/9818616.html
Copyright © 2011-2022 走看看