一道貪心:加括號使算式的值最大


問題描述

給定一個算術表達式形如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();
}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM