区 间 DP

让字符串为回文串的最少插入次数

public class Solution {

	// 暴力尝试
	public static int minInsertions(String str) {
		char[] s = str.toCharArray();
		int n = s.length;
		return f1(s, 0, n - 1);
	}

	// s[l....r]这个范围上的字符串,整体都变成回文串
	// 返回至少插入几个字符
	public static int f1(char[] s, int l, int r) {
		// l <= r
		if (l == r) {
			return 0;
		}
		if (l + 1 == r) {
			return s[l] == s[r] ? 0 : 1;
		}
		// l...r不只两个字符
		if (s[l] == s[r]) {
			return f1(s, l + 1, r - 1);
		} else {
			return Math.min(f1(s, l, r - 1), f1(s, l + 1, r)) + 1;
		}
	}

	// 记忆化搜索
	public static int minInsertions2(String str) {
		char[] s = str.toCharArray();
		int n = s.length;
		int[][] dp = new int[n][n];
		for (int i = 0; i < n; i++) {
			for (int j = i; j < n; j++) {
				dp[i][j] = -1;
			}
		}
		return f2(s, 0, n - 1, dp);
	}

	public static int f2(char[] s, int l, int r, int[][] dp) {
		if (dp[l][r] != -1) {
			return dp[l][r];
		}
		int ans;
		if (l == r) {
			ans = 0;
		} else if (l + 1 == r) {
			ans = s[l] == s[l + 1] ? 0 : 1;
		} else {
			if (s[l] == s[r]) {
				ans = f2(s, l + 1, r - 1, dp);
			} else {
				ans = Math.min(f2(s, l, r - 1, dp), f2(s, l + 1, r, dp)) + 1;
			}
		}
		dp[l][r] = ans;
		return ans;
	}

	// 严格位置依赖的动态规划
	public static int minInsertions3(String str) {
		char[] s = str.toCharArray();
		int n = s.length;
		int[][] dp = new int[n][n];
		for (int l = 0; l < n - 1; l++) {
			dp[l][l + 1] = s[l] == s[l + 1] ? 0 : 1;
		}
		for (int l = n - 3; l >= 0; l--) {
			for (int r = l + 2; r < n; r++) {
				if (s[l] == s[r]) {
					dp[l][r] = dp[l + 1][r - 1];
				} else {
					dp[l][r] = Math.min(dp[l][r - 1], dp[l + 1][r]) + 1;
				}
			}
		}
		return dp[0][n - 1];
	}

	// 空间压缩
	// 本题有关空间压缩的实现,可以参考讲解067,题目4,最长回文子序列问题的讲解
	// 这两个题空间压缩写法高度相似
	// 因为之前的课多次讲过空间压缩的内容,所以这里不再赘述
	public static int minInsertions4(String str) {
		char[] s = str.toCharArray();
		int n = s.length;
		if (n < 2) {
			return 0;
		}
		int[] dp = new int[n];
		dp[n - 1] = s[n - 2] == s[n - 1] ? 0 : 1;
		for (int l = n - 3, leftDown, backUp; l >= 0; l--) {
			leftDown = dp[l + 1];
			dp[l + 1] = s[l] == s[l + 1] ? 0 : 1;
			for (int r = l + 2; r < n; r++) {
				backUp = dp[r];
				if (s[l] == s[r]) {
					dp[r] = leftDown;
				} else {
					dp[r] = Math.min(dp[r - 1], dp[r]) + 1;
				}
				leftDown = backUp;
			}
		}
		return dp[n - 1];
	}

}

 先写递归区间l到r内变成回文的最小次数,然后将递归改为动态规划

预测赢家

public class Solution{

	// 暴力尝试
	public static boolean predictTheWinner(int[] nums) {
		int sum = 0;
		for (int num : nums) {
			sum += num;
		}
		int n = nums.length;
		int first = f1(nums, 0, n - 1);
		int second = sum - first;
		return first >= second;
	}

	// nums[l...r]范围上的数字进行游戏,轮到玩家1
	// 返回玩家1最终能获得多少分数,玩家1和玩家2都绝顶聪明
	public static int f1(int[] nums, int l, int r) {
		if (l == r) {
			return nums[l];
		}
		if (l == r - 1) {
			return Math.max(nums[l], nums[r]);
		}
		// l....r 不只两个数
		// 可能性1 :玩家1拿走nums[l] l+1...r
		int p1 = nums[l] + Math.min(f1(nums, l + 2, r), f1(nums, l + 1, r - 1));
		// 可能性2 :玩家1拿走nums[r] l...r-1
		int p2 = nums[r] + Math.min(f1(nums, l + 1, r - 1), f1(nums, l, r - 2));
		return Math.max(p1, p2);
	}

	// 记忆化搜索
	public static boolean predictTheWinner2(int[] nums) {
		int sum = 0;
		for (int num : nums) {
			sum += num;
		}
		int n = nums.length;
		int[][] dp = new int[n][n];
		for (int i = 0; i < n; i++) {
			for (int j = i; j < n; j++) {
				dp[i][j] = -1;
			}
		}
		int first = f2(nums, 0, n - 1, dp);
		int second = sum - first;
		return first >= second;
	}

	public static int f2(int[] nums, int l, int r, int[][] dp) {
		if (dp[l][r] != -1) {
			return dp[l][r];
		}
		int ans;
		if (l == r) {
			ans = nums[l];
		} else if (l == r - 1) {
			ans = Math.max(nums[l], nums[r]);
		} else {
			int p1 = nums[l] + Math.min(f2(nums, l + 2, r, dp), f2(nums, l + 1, r - 1, dp));
			int p2 = nums[r] + Math.min(f2(nums, l + 1, r - 1, dp), f2(nums, l, r - 2, dp));
			ans = Math.max(p1, p2);
		}
		dp[l][r] = ans;
		return ans;
	}

	// 严格位置依赖的动态规划
	public static boolean predictTheWinner3(int[] nums) {
		int sum = 0;
		for (int num : nums) {
			sum += num;
		}
		int n = nums.length;
		int[][] dp = new int[n][n];
		for (int i = 0; i < n - 1; i++) {
			dp[i][i] = nums[i];
			dp[i][i + 1] = Math.max(nums[i], nums[i + 1]);
		}
		dp[n - 1][n - 1] = nums[n - 1];
		for (int l = n - 3; l >= 0; l--) {
			for (int r = l + 2; r < n; r++) {
				dp[l][r] = Math.max(
						nums[l] + Math.min(dp[l + 2][r], dp[l + 1][r - 1]),
						nums[r] + Math.min(dp[l + 1][r - 1], dp[l][r - 2]));
			}
		}
		int first = dp[0][n - 1];
		int second = sum - first;
		return first >= second;
	}

}

写递归:从l到r先手玩家所能获得的最大数值,然后改写为动态规划

多边形三角形割分的最低得分

public class Code03_MinimumScoreTriangulationOfPolygon {

	// 记忆化搜索
	public static int minScoreTriangulation1(int[] arr) {
		int n = arr.length;
		int[][] dp = new int[n][n];
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				dp[i][j] = -1;
			}
		}
		return f(arr, 0, n - 1, dp);
	}

	public static int f(int[] arr, int l, int r, int[][] dp) {
		if (dp[l][r] != -1) {
			return dp[l][r];
		}
		int ans = Integer.MAX_VALUE;
		if (l == r || l == r - 1) {
			ans = 0;
		} else {
			// l....r >=3
			// 0..1..2..3..4...5
			for (int m = l + 1; m < r; m++) {
				// l m r
				ans = Math.min(ans, f(arr, l, m, dp) + f(arr, m, r, dp) + arr[l] * arr[m] * arr[r]);
			}
		}
		dp[l][r] = ans;
		return ans;
	}

	// 严格位置依赖的动态规划
	public static int minScoreTriangulation2(int[] arr) {
		int n = arr.length;
		int[][] dp = new int[n][n];
		for (int l = n - 3; l >= 0; l--) {
			for (int r = l + 2; r < n; r++) {
				dp[l][r] = Integer.MAX_VALUE;
				for (int m = l + 1; m < r; m++) {
					dp[l][r] = Math.min(dp[l][r], dp[l][m] + dp[m][r] + arr[l] * arr[m] * arr[r]);
				}
			}
		}
		return dp[0][n - 1];
	}

}

在区间内进行划分dp

  切棍子的最小成本

public class Code04_MinimumCostToCutAStick {

	// 记忆化搜索
	public static int minCost1(int n, int[] cuts) {
		int m = cuts.length;
		Arrays.sort(cuts);
		int[] arr = new int[m + 2];
		arr[0] = 0;
		for (int i = 1; i <= m; ++i) {
			arr[i] = cuts[i - 1];
		}
		arr[m + 1] = n;
		int[][] dp = new int[m + 2][m + 2];
		for (int i = 1; i <= m; i++) {
			for (int j = 1; j <= m; j++) {
				dp[i][j] = -1;
			}
		}
		return f(arr, 1, m, dp);
	}
	
	// 切点[l....r],决定一个顺序
	// 让切点都切完,总代价最小
	public static int f(int[] arr, int l, int r, int[][] dp) {
		if (l > r) {
			return 0;
		}
		if (l == r) {
			return arr[r + 1] - arr[l - 1];
		}
		if (dp[l][r] != -1) {
			return dp[l][r];
		}
		int ans = Integer.MAX_VALUE;
		for (int k = l; k <= r; k++) {
			ans = Math.min(ans, f(arr, l, k - 1, dp) + f(arr, k + 1, r, dp));
		}
		ans += arr[r + 1] - arr[l - 1];
		dp[l][r] = ans;
		return ans;
	}

	// 严格位置依赖的动态规划
	public static int minCost2(int n, int[] cuts) {
		int m = cuts.length;
		Arrays.sort(cuts);
		int[] arr = new int[m + 2];
		arr[0] = 0;
		for (int i = 1; i <= m; ++i) {
			arr[i] = cuts[i - 1];
		}
		arr[m + 1] = n;
		int[][] dp = new int[m + 2][m + 2];
		for (int i = 1; i <= m; i++) {
			dp[i][i] = arr[i + 1] - arr[i - 1];
		}
		for (int l = m - 1, next; l >= 1; l--) {
			for (int r = l + 1; r <= m; r++) {
				next = Integer.MAX_VALUE;
				for (int k = l; k <= r; k++) {
					next = Math.min(next, dp[l][k - 1] + dp[k + 1][r]);
				}
				dp[l][r] = arr[r + 1] - arr[l - 1] + next;
			}
		}
		return dp[1][m];
	}

}

  

 戳气球

public class Code05_BurstBalloons {

	// 记忆化搜索
	public static int maxCoins1(int[] nums) {
		int n = nums.length;
		// a b c d e
		// 1 a b c d e 1
		int[] arr = new int[n + 2];
		arr[0] = 1;
		arr[n + 1] = 1;
		for (int i = 0; i < n; i++) {
			arr[i + 1] = nums[i];
		}
		int[][] dp = new int[n + 2][n + 2];
		for (int i = 1; i <= n; i++) {
			for (int j = i; j <= n; j++) {
				dp[i][j] = -1;
			}
		}
		return f(arr, 1, n, dp);
	}

	// arr[l...r]这些气球决定一个顺序,获得最大得分返回!
	// 一定有 : arr[l-1]一定没爆!
	// 一定有 : arr[r+1]一定没爆!
	// 尝试每个气球最后打爆
	public static int f(int[] arr, int l, int r, int[][] dp) {
		if (dp[l][r] != -1) {
			return dp[l][r];
		}
		int ans;
		if (l == r) {
			ans = arr[l - 1] * arr[l] * arr[r + 1];
		} else {
			// l   ....r
			// l +1 +2 .. r
			ans = Math.max(
					arr[l - 1] * arr[l] * arr[r + 1] + f(arr, l + 1, r, dp), // l位置的气球最后打爆
					arr[l - 1] * arr[r] * arr[r + 1] + f(arr, l, r - 1, dp));// r位置的气球最后打爆
			for (int k = l + 1; k < r; k++) {
				// k位置的气球最后打爆
				// l...k-1  k  k+1...r
				ans = Math.max(ans, arr[l - 1] * arr[k] * arr[r + 1] + f(arr, l, k - 1, dp) + f(arr, k + 1, r, dp));
			}
		}
		dp[l][r] = ans;
		return ans;
	}

	// 严格位置依赖的动态规划
	public static int maxCoins2(int[] nums) {
		int n = nums.length;
		int[] arr = new int[n + 2];
		arr[0] = 1;
		arr[n + 1] = 1;
		for (int i = 0; i < n; i++) {
			arr[i + 1] = nums[i];
		}
		int[][] dp = new int[n + 2][n + 2];
		for (int i = 1; i <= n; i++) {
			dp[i][i] = arr[i - 1] * arr[i] * arr[i + 1];
		}
		for (int l = n, ans; l >= 1; l--) {
			for (int r = l + 1; r <= n; r++) {
				ans = Math.max(arr[l - 1] * arr[l] * arr[r + 1] + dp[l + 1][r],
						arr[l - 1] * arr[r] * arr[r + 1] + dp[l][r - 1]);
				for (int k = l + 1; k < r; k++) {
					ans = Math.max(ans, arr[l - 1] * arr[k] * arr[r + 1] + dp[l][k - 1] + dp[k + 1][r]);
				}
				dp[l][r] = ans;
			}
		}
		return dp[1][n];
	}

}

  

布尔运算

public class Code06_BooleanEvaluation {

	// 记忆化搜索
	public static int countEval(String str, int result) {
		char[] s = str.toCharArray();
		int n = s.length;
		int[][][] dp = new int[n][n][];
		int[] ft = f(s, 0, n - 1, dp);
		return ft[result];
	}

	// s[l...r]是表达式的一部分,且一定符合范式
	// 0/1  逻  0/1   逻       0/1
	//  l  l+1  l+2  l+3........r
	// s[l...r]  0 : ?
	//           1 : ?
	// ans : int[2] ans[0] = false方法数 ans[0] = true方法数
	public static int[] f(char[] s, int l, int r, int[][][] dp) {
		if (dp[l][r] != null) {
			return dp[l][r];
		}
		int f = 0;
		int t = 0;
		if (l == r) {
			// 只剩一个字符,0/1
			f = s[l] == '0' ? 1 : 0;
			t = s[l] == '1' ? 1 : 0;
		} else {
			int[] tmp;
			for (int k = l + 1, a, b, c, d; k < r; k += 2) {
				// l ... r
				// 枚举每一个逻辑符号最后执行 k = l+1 ... r-1  k+=2
				tmp = f(s, l, k - 1, dp);
				a = tmp[0];
				b = tmp[1];
				tmp = f(s, k + 1, r, dp);
				c = tmp[0];
				d = tmp[1];
				if (s[k] == '&') {
					f += a * c + a * d + b * c;
					t += b * d;
				} else if (s[k] == '|') {
					f += a * c;
					t += a * d + b * c + b * d;
				} else {
					f += a * c + b * d;
					t += a * d + b * c;
				}
			}
		}
		int[] ft = new int[] { f, t };
		dp[l][r] = ft;
		return ft;
	}

}

  括号的区间匹配

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        String str = br.readLine();
        out.println(compute(str));
        out.flush();
        out.close();
        br.close();
    }

    // 时间复杂度O(n^3)
    public static int compute(String str) {
        char[] s = str.toCharArray();
        int n = s.length;
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j] = -1;
            }
        }
        return f(s, 0, s.length - 1, dp);
    }

    // 让s[l...r]配对至少需要几个字符
    public static int f(char[] s, int l, int r, int[][] dp) {
        if (l == r) {
            return 1;
        }
        if (l == r - 1) {
            if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) {
                return 0;
            }
            return 2;
        }
        // l...r字符数量 >= 3
        if (dp[l][r] != -1) {
            return dp[l][r];
        }
        // 可能性1 : [l]、[r]本来就是配对的
        int p1 = Integer.MAX_VALUE;
        if ((s[l] == '(' && s[r] == ')') || (s[l] == '[' && s[r] == ']')) {
            p1 = f(s, l + 1, r - 1, dp);
        }
        // 可能性2 : 基于每个可能的划分点,做左右划分
        int p2 = Integer.MAX_VALUE;
        for (int m = l; m < r; m++) {
            p2 = Math.min(p2, f(s, l, m, dp) + f(s, m + 1, r, dp));
        }
        int ans = Math.min(p1, p2);
        dp[l][r] = ans;
        return ans;
    }

}

奇怪的打印机

public class Solution {

    public static int strangePrinter(String str) {
        char[] s = str.toCharArray();
        int n = s.length;
        int[][] dp = new int[n][n];
        dp[n - 1][n - 1] = 1;
        for (int i = 0; i < n - 1; i++) {
            dp[i][i] = 1;
            dp[i][i + 1] = s[i] == s[i + 1] ? 1 : 2;
        }
        for (int l = n - 3, ans; l >= 0; l--) {
            for (int r = l + 2; r < n; r++) {
                // dp[l][r]
                if (s[l] == s[r]) {//相等时r或l位置上的可以看作被l或r位置上的一起打印上的 
                    dp[l][r] = dp[l][r - 1];
                    // dp[l][r] = dp[l + 1][r];
                } else {//不相等时那么l-r一定是分俩部分打印上的,枚举分段点
                    ans = Integer.MAX_VALUE;
                    for (int m = l; m < r; m++) {
                        ans = Math.min(ans, dp[l][m] + dp[m + 1][r]);
                    }
                    dp[l][r] = ans;
                }
            }
        }
        return dp[0][n - 1];
    }

}

 合唱队


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

public class Main {

	public static int MAXN = 1001;

	public static int[] nums = new int[MAXN];

	public static int[][] dp = new int[MAXN][2];

	public static int n;

	public static int MOD = 19650827;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		while (in.nextToken() != StreamTokenizer.TT_EOF) {
			n = (int) in.nval;
			for (int i = 1; i <= n; i++) {
				in.nextToken();
				nums[i] = (int) in.nval;
			}
			if (n == 1) {
				out.println(1);
			} else {
				out.println(compute2());
			}
		}
		out.flush();
		out.close();
		br.close();
	}

	// 时间复杂度O(n^2)
	// 严格位置依赖的动态规划
	public static int compute1() {
		// 人的编号范围 : 1...n
		// dp[l][r][0] : 形成l...r的状况的方法数,同时要求l位置的数字是最后出现的
		// dp[l][r][1] : 形成l...r的状况的方法数,同时要求r位置的数字是最后出现的
		int[][][] dp = new int[n + 1][n + 1][2];
		for (int i = 1; i < n; i++) {
			if (nums[i] < nums[i + 1]) {
				dp[i][i + 1][0] = 1;
				dp[i][i + 1][1] = 1;
			}
		}
		for (int l = n - 2; l >= 1; l--) {
			for (int r = l + 2; r <= n; r++) {
				if (nums[l] < nums[l + 1]) {
					dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][0]) % MOD;
				}
				if (nums[l] < nums[r]) {
					dp[l][r][0] = (dp[l][r][0] + dp[l + 1][r][1]) % MOD;
				}
				if (nums[r] > nums[l]) {
					dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][0]) % MOD;
				}
				if (nums[r] > nums[r - 1]) {
					dp[l][r][1] = (dp[l][r][1] + dp[l][r - 1][1]) % MOD;
				}
			}
		}
		return (dp[1][n][0] + dp[1][n][1]) % MOD;
	}

	// 时间复杂度O(n^2)
	// 空间压缩
	public static int compute2() {
		if (nums[n - 1] < nums[n]) {
			dp[n][0] = 1;
			dp[n][1] = 1;
		}
		for (int l = n - 2; l >= 1; l--) {
			if (nums[l] < nums[l + 1]) {
				dp[l + 1][0] = 1;
				dp[l + 1][1] = 1;
			} else {
				dp[l + 1][0] = 0;
				dp[l + 1][1] = 0;
			}
			for (int r = l + 2; r <= n; r++) {
				int a = 0;
				int b = 0;
				if (nums[l] < nums[l + 1]) {
					a = (a + dp[r][0]) % MOD;
				}
				if (nums[l] < nums[r]) {
					a = (a + dp[r][1]) % MOD;
				}
				if (nums[r] > nums[l]) {
					b = (b + dp[r - 1][0]) % MOD;
				}
				if (nums[r] > nums[r - 1]) {
					b = (b + dp[r - 1][1]) % MOD;
				}
				dp[r][0] = a;
				dp[r][1] = b;
			}
		}
		return (dp[n][0] + dp[n][1]) % MOD;
	}

}

  移除盒子

public class Code04_RemoveBoxes {

	// 时间复杂度O(n^4)
	public static int removeBoxes(int[] boxes) {
		int n = boxes.length;
		int[][][] dp = new int[n][n][n];
		return f(boxes, 0, n - 1, 0, dp);
	}

	// boxes[l....r]范围上要去消除,前面跟着k个连续的和boxes[l]颜色一样的盒子
	// 这种情况下,返回最大得分
	public static int f(int[] boxes, int l, int r, int k, int[][][] dp) {
		if (l > r) {
			return 0;
		}
		// l <= r
		if (dp[l][r][k] > 0) {
			return dp[l][r][k];
		}
		int s = l;
		while (s + 1 <= r && boxes[l] == boxes[s + 1]) {
			s++;
		}
		// boxes[l...s]都是一种颜色,boxes[s+1]就不是同一种颜色了
		// cnt是总前缀数量 : 之前的相同前缀(k个) + l...s这个颜色相同的部分(s-l+1个)
		int cnt = k + s - l + 1;
		// 可能性1 : 前缀先消
		int ans = cnt * cnt + f(boxes, s + 1, r, 0, dp);
		// 可能性2 : 讨论前缀跟着哪个后,一起消掉
		for (int m = s + 2; m <= r; m++) {
			if (boxes[l] == boxes[m] && boxes[m - 1] != boxes[m]) {
				// boxes[l] == boxes[m]是必须条件
				// boxes[m - 1] != boxes[m]是剪枝条件,避免不必要的调用
				ans = Math.max(ans, f(boxes, s + 1, m - 1, 0, dp) + f(boxes, m, r, cnt, dp));
			}
		}
		dp[l][r][k] = ans;
		return ans;
	}

}

带前缀信息的区间dp

合并石头的最低成本

public class Solution {

	// 时间复杂度O(n^3)
	// 优化策略来自于观察
	// l.....r最终会变成几份其实是注定的,根本就无法改变
	// 那么也就知道,满足(n - 1) % (k - 1) == 0的情况下,
	// 0....n-1最终一定是1份,也无法改变
	// 如果l.....r最终一定是1份
	// 那么要保证l.....m最终一定是1份,m+1...r最终一定是k-1份
	// 如果l.....r最终一定是p份(p>1)
	// 那么要保证l.....m最终一定是1份,那么m+1...r最终一定是p-1份
	// 怎么保证的?枚举行为中,m += k-1很重要!
	// m每次跳k-1!
	// 如果l.....r最终一定是1份
	// 就一定能保证l.....m最终一定是1份
	// 也一定能保证m+1...r最终一定是k-1份
	// 不要忘了,加上最后合并成1份的代价
	// 如果l.....r最终一定是p份
	// 就一定能保证l.....m最终一定是1份
	// 也一定能保证m+1...r最终一定是p-1份
	// 不用加上最后合并成1份的代价
	public static int mergeStones(int[] stones, int k) {
		int n = stones.length;
		if ((n - 1) % (k-1)!= 0) {
			return -1;
		}
		int[] presum = new int[n + 1];
		// 多补了一个0位置,l...r累加和 : presum[r+1] - presum[l]
		for (int i = 0, j = 1, sum = 0; i < n; i++, j++) {
			sum += stones[i];
			presum[j] = sum;
		}
		// dp[l][r] : l...r范围上的石头,合并到不能再合并(份数是确定的),最小代价是多少
		int[][] dp = new int[n][n];
		for (int l = n - 2, ans; l >= 0; l--) {
			for (int r = l + 1; r < n; r++) {
				ans = Integer.MAX_VALUE;
				for (int m = l; m < r; m += k - 1) {
					ans = Math.min(ans, dp[l][m] + dp[m + 1][r]);
				}
				if ((r - l) % (k - 1) == 0) {
					// 最终一定能划分成一份,那么就再加合并代价
					ans += presum[r + 1] - presum[l];
				}
				dp[l][r] = ans;
			}
		}
		return dp[0][n - 1];
	}

}

不同的回文子序列

public class Code06_CountDifferentPalindromicSubsequences {

	// 时间复杂度O(n^2)
	public static int countPalindromicSubsequences(String str) {
		int mod = 1000000007;
		char[] s = str.toCharArray();
		int n = s.length;
		int[] last = new int[256];
		// left[i] : i位置的左边和s[i]字符相等且最近的位置在哪,不存在就是-1
		int[] left = new int[n];
		Arrays.fill(last, -1);
		for (int i = 0; i < n; i++) {
			left[i] = last[s[i]];
			last[s[i]] = i;
		}
		// right[i] : i位置的右边和s[i]字符相等且最近的位置在哪,不存在就是n
		int[] right = new int[n];
		Arrays.fill(last, n);
		for (int i = n - 1; i >= 0; i--) {
			right[i] = last[s[i]];
			last[s[i]] = i;
		}
		// dp[i][j] : i...j范围上有多少不同的回文子序列
		// 如果i>j,那么认为是无效范围dp[i][j] = 0
		long[][] dp = new long[n][n];
		for (int i = 0; i < n; i++) {
			dp[i][i] = 1;
		}
		for (int i = n - 2, l, r; i >= 0; i--) {
			for (int j = i + 1; j < n; j++) {
				if (s[i] != s[j]) {
					// a ..... b
					// i       j
					// 因为要取模,所以只要发生减操作就+mod,讲解041同余原理
					dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1] + mod;
				} else {
					// s[i] == s[j]
					// a......a
					// i      j
					l = right[i];
					r = left[j];
					if (l > r) {
						// i...j的内部没有s[i]字符
						// a....a
						// i    j
						// (i+1..j-1) + a(i+1..j-1)a + a + aa
						dp[i][j] = dp[i + 1][j - 1] * 2 + 2;
					} else if (l == r) {
						// i...j的内部有一个s[i]字符
						// a.....a......a
						// i     lr     j
						// (i+1..j-1) + a(i+1..j-1)a + aa
						dp[i][j] = dp[i + 1][j - 1] * 2 + 1;
					} else {
						// i...j的内部不只一个s[i]字符
						// a...a....这内部可能还有a但是不重要....a...a
						// i   l                             r   j
						// 因为要取模,所以只要发生减操作就+mod,讲解041同余原理
						dp[i][j] = dp[i + 1][j - 1] * 2 - dp[l + 1][r - 1] + mod;
					}
				}
				dp[i][j] %= mod;
			}
		}
		return (int) dp[0][n - 1];
	}

}

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值