public class Code_SplitArrayLargestSum {

    // 求原数组arr[L...R]的累加和
    public static int sum(int[] sum, int L, int R) {
        return sum[R + 1] - sum[L];
    }

    // 不优化枚举的动态规划方法,O(N^2 * K)
    public static int splitArray1(int[] nums, int K) {
        int N = nums.length;
        int[] sum = new int[N + 1];
        for (int i = 0; i < N; i++) {
            sum[i + 1] = sum[i] + nums[i];
        }
        int[][] dp = new int[N][K + 1];
        for (int j = 1; j <= K; j++) {
            dp[0][j] = nums[0];
        }
        for (int i = 1; i < N; i++) {
            dp[i][1] = sum(sum, 0, i);
        }
        // 每一行从上往下
        // 每一列从左往右
        // 根本不去凑优化位置对儿!
        for (int i = 1; i < N; i++) {
            for (int j = 2; j <= K; j++) {
                int ans = Integer.MAX_VALUE;
                // 枚举是完全不优化的!
                for (int leftEnd = 0; leftEnd <= i; leftEnd++) {
                    int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1];
                    int rightCost = leftEnd == i ? 0 : sum(sum, leftEnd + 1, i);
                    int cur = Math.max(leftCost, rightCost);
                    if (cur < ans) {
                        ans = cur;
                    }
                }
                dp[i][j] = ans;
            }
        }
        return dp[N - 1][K];
    }

    // 课上现场写的方法,用了枚举优化,O(N * K)
    public static int splitArray2(int[] nums, int K) {
        int N = nums.length;
        int[] sum = new int[N + 1];
        for (int i = 0; i < N; i++) {
            sum[i + 1] = sum[i] + nums[i];
        }
        int[][] dp = new int[N][K + 1];
        int[][] best = new int[N][K + 1];
        // 第0行
        for (int j = 1; j <= K; j++) {
            dp[0][j] = nums[0];
            best[0][j] = -1;
        }

        // 第1列
        for (int i = 1; i < N; i++) {
            dp[i][1] = sum(sum,0,i);
            best[i][1] = -1;
        }

        for (int j = 2; j <= K; j++) {
            for (int i = N-1; i >=1; i--) {
                int up = (i == N-1) ? N-1 : best[i+1][j];
                int down = best[i][j-1];
                int ans = Integer.MAX_VALUE;
                int bestChose = -1;
                for (int leftEnd = down; leftEnd <= up; leftEnd++) {
                    int leftCost = (leftEnd == -1) ? 0 : dp[leftEnd][j-1];
                    int rightCost = (leftEnd == i) ? 0 : sum(sum,leftEnd+1,i);
                    int cur = Math.max(leftCost,rightCost);
                    if(cur < ans){
                        ans = cur;
                        bestChose = leftEnd;
                    }
                }

                dp[i][j] = ans;
                best[i][j] = bestChose;
            }
        }
        return dp[N-1][K];
    }

    public static int splitArray3(int[] nums, int M) {
        long sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        long l = 0;
        long r = sum;
        long ans = 0;
        while (l <= r) {
            long mid = (l + r) / 2;
            long cur = getNeedParts(nums, mid);
            if (cur <= M) {
                ans = mid;
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        return (int) ans;
    }

    public static int getNeedParts(int[] arr, long aim) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > aim) {
                return Integer.MAX_VALUE;
            }
        }
        int parts = 1;
        int all = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (all + arr[i] > aim) {
                parts++;
                all = arr[i];
            } else {
                all += arr[i];
            }
        }
        return parts;
    }

    public static int[] randomArray(int len, int maxValue) {
        int[] arr = new int[len];
        for (int i = 0; i < len; i++) {
            arr[i] = (int) (Math.random() * maxValue);
        }
        return arr;
    }

    public static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int N = 100;
        int maxValue = 100;
        int testTime = 10000;
        System.out.println("测试开始 Code_SplitArrayLargestSum");
        for (int i = 0; i < testTime; i++) {
            int len = (int) (Math.random() * N) + 1;
            int M = (int) (Math.random() * N) + 1;
            int[] arr = randomArray(len, maxValue);
            int ans1 = splitArray1(arr, M);
            int ans2 = splitArray2(arr, M);
            int ans3 = splitArray3(arr, M);
            if (ans1 != ans2 || ans1 != ans3) {
                System.out.print("arr : ");
                printArray(arr);
                System.out.println("M : " + M);
                System.out.println("ans1 : " + ans1);
                System.out.println("ans2 : " + ans2);
                System.out.println("ans3 : " + ans3);
                System.out.println("Oops!");
                break;
            }
        }
        System.out.println("测试结束 Code_SplitArrayLargestSum");
    }
}