学习动态规划
常用模型
背包
线性DP
计数类DP
数位统计DP
状态压缩DP
树形DP
记忆化搜索
闫式DP 没有模板,理解分析方式(集合的思想)
背包 模板问题:有n个物品和容量为v的背包,每件物品有体积vi和价值wi,问能装下的最大价值w
01背包 每件物品只能用1次(0,1)
Dp
:
得到公式:dp[i][j] = Math.max(dp[i][j], dp[i-1][j-v[i]] + w[i]);
二维dp实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.io.*;public class Main { static int N = 1010 ; static int n, m; static int [] v = new int [N]; static int [] w = new int [N]; static int [][] dp = new int [N][N]; public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] str = br.readLine().split(" " ); int n = Integer.parseInt(str[0 ]); int m = Integer.parseInt(str[1 ]); for (int i = 1 ; i <= n; i++){ String[] s = br.readLine().split(" " ); v[i] = Integer.parseInt(s[0 ]); w[i] = Integer.parseInt(s[1 ]); } for (int i = 1 ; i <= n; i++){ for (int j = 0 ; j <= m; j++){ dp[i][j] = dp[i-1 ][j]; if (j >= v[i]){ dp[i][j] = Math.max(dp[i][j], dp[i-1 ][j-v[i]] + w[i]); } } } System.out.println(dp[n][m]); } }
dp优化:二维等价成一维
因为:i只用到了i-1,将i删去,交换循环方向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.io.*;public class Main { static int N = 1010 ; static int n, m; static int [] v = new int [N]; static int [] w = new int [N]; static int [] dp = new int [N]; public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] str = br.readLine().split(" " ); int n = Integer.parseInt(str[0 ]); int m = Integer.parseInt(str[1 ]); for (int i = 1 ; i <= n; i++){ String[] s = br.readLine().split(" " ); v[i] = Integer.parseInt(s[0 ]); w[i] = Integer.parseInt(s[1 ]); } for (int i = 1 ; i <= n; i++){ for (int j = m; j >= v[i]; j--){ dp[j] = Math.max(dp[j], dp[j-v[i]] + w[i]); } } System.out.println(dp[m]); } }
完全背包 每件物品有无限个
Dp
:
三维实现:
1 2 3 4 5 for (int i = 1 ; i <= n; i++) for (int j = 0 ; j <= m; j++) for (int k = 0 ; k * v[i] <= j; k++) f[i][j] = Math.max(f[i][j], f[i-1 ][j - k * v[i]] + k * w[i]); System.out.println(f[n][m]);
dp优化:
得到公式:f(i, j) = Max(f[i - 1, j], f[i, j - v] + w)
可以理解为分为不含i的情况 + 至少含一次i的情况
1 2 3 4 5 6 for (int i = 1 ; i <= n; i++) for (int j = 0 ; j <= m; j++) f[i][j] = f[i-1 ][j]; if (j >= v[i]) f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]); System.out.println(f[n][m]);
可以看出01背包问题是i - 1,完全背包问题是i
那么完全背包也可以优化成一维:
1 2 3 for (int i = 1 ; i <= n; i++) for (int j = v[i]; j <= m; j++) dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]);
多重背包 限定每个物品有si个(有限)
Dp
:
朴素三位和完全背包类似:
复杂度: O(v w s)
1 2 3 4 5 for (int i = 1 ; i <= n; i++) for (int j = 0 ; j <= m; j++) for (int k = 0 ; k <= s[i] && k * v[i] <= j; k++) f[i][j] = Math.max(f[i][j], f[i-1 ][j - k * v[i]] + k * w[i]); System.out.println(f[n][m]);
然而多重背包问题不能用完全背包优化 , 因为是多重背包不能确定是否有最后一项(因为s不一定可使背包边满),而完全背包一定每有最后一项。
二进制优化
将s[i]分组,以二进制为标准,分成1, 2, 4, 8, ..., 2^(k), c
,其中c为s[i] - (2^(k+1) - 1)
可以证明,这组数可以表示0~s[i]之间的所有数
因为:1, 2, 4, 8, ..., 2^(k)
可表示[0, 2^(k+1) - 1]
中所有的数;加上c可表示[c, s[i]]
中所有的数;而c <= 2 ^ (k+1)
,两区间可以合并
这样对于每个s[i],可以将其分为log(s[i])个数分别考虑选择,这样就可以将问题转换为n扩大到n * log(s[i])个数后进行01背包
复杂度:O(n m log[s])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import java.io.*;public class Main { static int N = 11010 ; static int M = 2010 ; static int [] v = new int [N]; static int [] w = new int [N]; static int [] dp = new int [M]; public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] str = br.readLine().split(" " ); int n = Integer.parseInt(str[0 ]); int m = Integer.parseInt(str[1 ]); int cnt = 0 ; for (int i = 1 ; i <= n; i++){ String[] arr = br.readLine().split(" " ); int a = Integer.parseInt(arr[0 ]); int b = Integer.parseInt(arr[1 ]); int s = Integer.parseInt(arr[2 ]); int k = 1 ; while (k <= s){ cnt++; v[cnt] = a * k; w[cnt] = b * k; s -= k; k *= 2 ; } if (s > 0 ){ cnt++; v[cnt] = a * s; w[cnt] = b * s; } } n = cnt; for (int i = 1 ; i <= n; i++){ for (int j = m; j >= v[i]; j--){ dp[j] = Math.max(dp[j], dp[j - v[i]] + w[i]); } } System.out.println(dp[m]); } }
分组背包问题 有n组物品,每组物品有若干个,但每组只能选一个物品
Dp
:
状态表示f(i,j)
集合
表示所有选法
条件:
只从前i组 物品中选
总体积<=j
属性:
Max 、Min、数量
状态计算
->集合划分:f(i,j)
-> 第i组没有选的情况f(i - 1, j)
+ 第i组选了第k个的情况f(i - 1, j - v[i,k]) + w[i,k]
复杂度O(n^3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.io.*;public class Main { static int N = 110 ; static int [][] v = new int [N][N]; static int [][] w = new int [N][N]; static int [] s = new int [N]; static int [] dp = new int [N]; public static void main (String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] str = br.readLine().split(" " ); int n = Integer.parseInt(str[0 ]); int m = Integer.parseInt(str[1 ]); for (int i = 1 ; i <= n; i++){ s[i] = Integer.parseInt(br.readLine()); for (int j = 1 ; j <= s[i]; j++){ String[] arr = br.readLine().split(" " ); v[i][j] = Integer.parseInt(arr[0 ]); w[i][j] = Integer.parseInt(arr[1 ]); } } for (int i = 1 ; i <= n; i++){ for (int j = m; j >= 0 ; j--){ for (int k = 1 ; k <= s[i]; k++){ if (j >= v[i][k])dp[j] = Math.max(dp[j], dp[j - v[i][k]] + w[i][k]); } } } System.out.println(dp[m]); } }
线性DP 最长上升序列 给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
Dp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import java.util.Scanner;import java.util.Arrays;public class Main { static int N = 1010 ; static int [] a = new int [N]; static int [] dp = new int [N]; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i++)a[i] = sc.nextInt(); Arrays.fill(dp, 1 ); for (int i = 2 ; i <= n; i++){ for (int j = 1 ; j < i; j++){ if (a[i] > a[j])dp[i] = Math.max(dp[i], dp[j] + 1 ); } } int res = 0 ; for (int i = 1 ; i <= n; i++)res = Math.max(res, dp[i]); System.out.println(res); } }
最长公共子序列 给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
Dp
:
状态表示f(i,j)
集合
所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列
属性:是Max
状态计算
->集合划分:以a[i] b[j] 是否被选划分集合为4中情况
00: dp[i-1, j-1]
11: dp[i-1, j-1] + 1
01: dp[i-1, j]
包含了 a[i]不出现,b[j]出现的情况(01)
10: dp[i, j-1]
包含了 a[i]出现,b[j]不出现的情况(10)
而01和10的情况中自然包含了00情况,于是代码中可以不写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.util.Scanner;public class Main { static int N = 1010 ; static int [][] f = new int [N][N]; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int m = sc.nextInt(); String a = sc.next(); String b = sc.next(); for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= m; j++){ f[i][j] = Math.max(f[i-1 ][j], f[i][j-1 ]); if (a.charAt(i-1 ) == b.charAt(j-1 ))f[i][j] = Math.max(f[i][j], f[i-1 ][j-1 ] + 1 ); } } System.out.println(f[n][m]); } }
最短编辑距离 给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。
Dp
:
状态表示f(i,j)
状态计算:
集合划分:根据最后一步操作
删除a[i]
:f[i-1][j] + 1
插入a[i]
:f[i][j-1] + 1
替换a[i]
:f[i-1][j-1] + 1
复杂度:O(3*n^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import java.util.Arrays;import java.util.Scanner;public class Main { static int N = 1010 ; static int [][] f = new int [N][N]; static int INF = (int )1e9 ; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String a = sc.next(); int m = sc.nextInt(); String b = sc.next(); for (int i = 0 ; i <= n; i++)f[i][0 ] = i; for (int j = 0 ; j <= m; j++)f[0 ][j] = j; for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= m; j++) { f[i][j] = Math.min(f[i - 1 ][j] + 1 , f[i][j - 1 ] + 1 ); if (a.charAt(i - 1 ) == b.charAt(j - 1 )) f[i][j] = Math.min(f[i][j], f[i - 1 ][j - 1 ]); f[i][j] = Math.min(f[i][j], f[i - 1 ][j - 1 ] + 1 ); } } System.out.println(f[n][m]); } }
区间DP 区间 DP 常用模版 所有的区间dp问题枚举时,第一维通常是枚举区间长度,并且一般 len = 1 时用来初始化,枚举从 len = 2 开始;第二维枚举起点 i (右端点 j 自动获得,j = i + len - 1)
模板代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 for (int len = 1 ; len <= n; len++) { for (int i = 1 ; i + len - 1 <= n; i++) { int j = i + len - 1 ; if (len == 1 ) { dp[i][j] = 初始值 continue ; } for (int k = i; k < j; k++) { dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1 ][j] + w[i][j]); } } }
Dp
:
状态表示f(i,j)
集合
所有将第i堆石子到第j对石子合并成一堆石子的合并方式
属性:是Min
状态计算
->集合划分:以最后一步是左边哪一部分和右边哪一部分合并,即以分界线划分成j - i组 [i, k], [k + 1, j] k = [i, j-1]
f[i][j] = min(f[i][k] + f[k+1][j] + s[j] - s[i-1])
复杂度:O(N^3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.util.Scanner;public class Main { static int N = 310 ; static int [] s = new int [N]; static int [][] f = new int [N][N]; static int INF = (int )2e9 ; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i++){ s[i] = sc.nextInt(); s[i] += s[i - 1 ]; } for (int len = 2 ; len <= n; len++){ for (int i = 1 ; i <= n - len + 1 ; i++){ int l = i, r = i + len - 1 ; f[l][r] = INF; for (int k = l; k < r; k++){ f[l][r] = Math.min(f[l][r], f[l][k] + f[k + 1 ][r] + s[r] - s[l - 1 ]); } } } System.out.println(f[1 ][n]); } }
计数类DP 整数拆分 一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk
,其中 n1≥n2≥…≥nk,k≥1
。 我们将这样的一种表示称为正整数 n 的一种划分。 现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。
方法一 :理解为完全背包问题
Dp
:
状态表示f(i,j)
状态计算
->集合划分:以选择i的数量为划分依据,分为不选i,选s个i
状态转移:f[i][j] = (f[i - 1][j] + sum(f[i - 1][j - s * i]))
其中s * i <= j
发现可以同完全背包问题一样得到优化如下:
f[i][j] = (f[i - 1][j] + f[i][j - i])
也可变成一维:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.util.Scanner;public class Main { static int N = 1010 ; static int [] f = new int [N]; static int mod = (int )1e9 + 7 ; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); f[0 ] = 1 ; for (int i = 1 ; i <= n; i++){ for (int j = i; j <= n; j++){ f[j] = (f[j] + f[j - i]) % mod; } } System.out.println(f[n]); } }
方法二:
Dp
:
状态表示f(i,j)
集合
所有总和是i,并且恰好表示成j个正整数的和的方案数
属性:数量
状态计算
->集合划分:j个数的最小值是否为1
为1,将1去掉得到f[i-1][j-1]
;不为1,将所有数减1,得到f[i - j][j]
状态转移:f[i][j] = f[i-1][j-1] + f[i - j][j]
最终答案是res = sum(f[n][1~n])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.util.Scanner;public class Main { static int N = 1010 ; static int [][] f = new int [N][N]; static int mod = (int )1e9 + 7 ; public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); f[0 ][0 ] = 1 ; for (int i = 1 ; i <= n; i++){ for (int j = 1 ; j <= i; j++){ f[i][j] = (f[i - 1 ][j - 1 ] + f[i - j][j]) % mod; } } int res = 0 ; for (int i = 1 ; i <= n; i++)res = (res + f[n][i]) % mod; System.out.println(res); } }
数位统计DP 计数问题:
给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。
0<a,b<100000000
求一个函数count(n ,x)表示1~n中x出现的次数(x = 0 ~ 9)
分情况讨论 :
边界问题:
当所求x出现在最高位时,没有(1)的情况
当所求x = 0时,为了规避前导零的情况,abc != 0, 故是abc * 999
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import java.util.Scanner;import java.util.ArrayList;import java.util.List;public class Main { static int N = 10 ; static int get (List<Integer> list, int l, int r) { int res = 0 ; for (int i = l; i >= r; i--){ res = res * 10 + list.get(i); } return res; } static int power10 (int x) { int res = 1 ; while (x != 0 ){ res *= 10 ; x--; } return res; } static int count (int n, int x) { if (n == 0 )return 0 ; List<Integer> l = new ArrayList<>(); while (n != 0 ){ l.add(n % 10 ); n /= 10 ; } int len = l.size(); int res = 0 ; for (int i = len - 1 - (x == 0 ? 1 : 0 ); i >= 0 ; i--){ if (i < len - 1 ){ res += get(l, len - 1 , i + 1 ) * power10(i); if (x == 0 )res -= power10(i); } if (l.get(i) > x)res += power10(i); else if (l.get(i) == x) res += get(l, i - 1 , 0 ) + 1 ; } return res; } public static void main (String[] args) { Scanner sc = new Scanner(System.in); while (true ){ int a = sc.nextInt(); int b = sc.nextInt(); if (a == 0 && b == 0 )break ; if (a > b){ int t = a; a = b; b = t; } for (int i = 0 ; i <= 9 ; i++){ System.out.print(count(b, i) - count(a - 1 , i) + " " ); } System.out.println(); } } }
状态压缩DP 例题:
蒙德里安的梦想 求把 N×MN×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。
例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。
如下图所示:
问给定N,M后的方案数。
1≤N,M≤11
解:
核心:先放横着的,再放竖着的
总方案数:只放横着的小方块的合法方案数
合法判断:所有剩余位置能否填充满竖着的小方块->可以按列来看。每一列内部所有连续的空着的小方块,需要是偶数个
Dp
:
状态表示f(i,j)
集合
已经将前i-1列摆好,且从第i-1列,伸出到第i列的状态是j(二进制)的所有方案
属性:数量
状态计算
->集合划分:根据i-1列伸出的小方格状态划分为2^n种情况
条件:
(1)(j&k) == 0(i-2列伸到i-1列的状态是k)
(2)所有连续空着的位置的长度必须是偶数
最终答案:f[m][0]
第m+1列状态都为0的所有方案
时间复杂度:O(11 2^11 2^11)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import java.io.*;import java.util.*;public class Main { static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); static int N = 12 , M = 1 << N; static long [][] f = new long [N][M]; static boolean [][] state = new boolean [M][M]; static boolean [] st = new boolean [M]; public static void main (String[] args) throws IOException { while (true ){ String[] str = br.readLine().split(" " ); int n = Integer.parseInt(str[0 ]); int m = Integer.parseInt(str[1 ]); if (n == 0 && m == 0 )return ; for (int i = 0 ; i < 1 << n; i++){ int cnt = 0 ; boolean flag = true ; for (int j = 0 ; j < n; j++){ if ((i >> j & 1 ) == 1 ){ if (cnt % 2 == 1 ){ flag = false ; break ; } cnt = 0 ; }else cnt++; } if (cnt % 2 == 1 )flag = false ; st[i] = flag; } for (int i = 0 ; i < 1 << n; i++){ Arrays.fill(state[i], false ); for (int j = 0 ; j < 1 << n; j++){ if ((i & j) == 0 && st[i | j]){ state[i][j] = true ; } } } for (int i = 0 ; i < N; i++)Arrays.fill(f[i], 0 ); f[0 ][0 ] = 1 ; for (int i = 1 ; i <= m; i++){ for (int j = 0 ; j < 1 << n; j++){ for (int k = 0 ; k < 1 << n; k++){ if (state[j][k])f[i][j] += f[i - 1 ][k]; } } } System.out.println(f[m][0 ]); } } }
最短Hamilton路径 给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。 Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。
输出一个整数,表示最短 Hamilton 路径的长度。1≤n≤20 0≤a[i,j]≤107
Dp
:
状态表示f(i,j)
集合
所有从0走到j,走过的所有点状态是i(二进制 表示是否走过)的所有路径
属性:Min
状态计算
->集合划分:倒数第二个点是k (0,1,2,…,m)
Min(f(i - {j}, k) )+ a(k, j)
复杂度:n * n * 2 ^ n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import java.io.*;import java.util.Arrays;public class Main { static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); static int N = 20 ; static int M = 1 << N; static int [][] w = new int [N][N]; static int [][] f = new int [M][N]; public static void main (String[] args) throws IOException { int n = Integer.parseInt(br.readLine()); for (int i = 0 ; i < n; i++){ String[] str = br.readLine().split(" " ); for (int j = 0 ; j < n; j++){ w[i][j] = Integer.parseInt(str[j]); } } for (int i = 0 ; i < 1 << n; i++){ Arrays.fill(f[i], 0x3f3f3f3f ); } f[1 ][0 ] = 0 ; for (int i = 0 ; i < 1 << n; i++){ for (int j = 0 ; j < n; j++){ if ((i >> j & 1 ) != 0 ){ for (int k = 0 ; k < n; k++){ if ((i - (1 << j) >> k & 1 ) != 0 ) f[i][j] = Math.min(f[i][j], f[i - (1 << j)][k] + w[k][j]); } } } } System.out.println(f[(1 << n) - 1 ][n-1 ]); } }
树形DP 没有上司的舞会 Ural 大学有 N 名职员,编号为 1∼N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。 在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入格式 第一行一个整数 N。
接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。
接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。
输出格式 输出最大的快乐指数。
数据范围 1≤N≤6000, −128≤Hi≤127
Dp
:
状态表示f(u, 0 / 1)
集合
f(u,0)所有从以u为根的子树中选择,并且不选u这个点的方案
f(u,1)所有从以u为根的子树中选择,并且选择u这个点的方案
属性:Min
状态计算(设u的子节点为si
)
f(u, 0) = sum(max(f(si, 0), f(si, 1)))
f(u, 1) = sum(f(si, 0))
复杂度:O(n)
(状态数:2n
,所有节点儿子的数量(总枚举次数:n-1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import java.util.Scanner;public class Main { static int N = 6010 ; static int [][] f = new int [N][2 ]; static int [] happy = new int [N]; static int [] h = new int [N]; static int [] e = new int [N]; static int [] ne = new int [N]; static int idx = 1 ; static boolean [] has_father = new boolean [N]; static void add (int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } static void dfs (int u) { f[u][1 ] = happy[u]; for (int i = h[u]; i != 0 ; i = ne[i]){ int j = e[i]; dfs(j); f[u][0 ] += Math.max(f[j][0 ], f[j][1 ]); f[u][1 ] += f[j][0 ]; } } public static void main (String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); for (int i = 1 ; i <= n; i++)happy[i] = sc.nextInt(); for (int i = 1 ; i < n; i++){ int a = sc.nextInt(); int b = sc.nextInt(); add(b, a); has_father[a] = true ; } int root = 1 ; while (has_father[root])root++; dfs(root); System.out.println(Math.max(f[root][0 ], f[root][1 ])); } }
记忆化搜索 滑雪 Dp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.io.*;public class Main { static int N = 310 ; static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); static int [][] a = new int [N][N]; static int [][] f = new int [N][N]; static int [] dx = {-1 , 0 , 1 , 0 }; static int [] dy = {0 , 1 , 0 , -1 }; static int n, m; static int dfs (int x, int y) { if (f[x][y] != 0 )return f[x][y]; f[x][y] = 1 ; for (int i = 0 ; i < 4 ; i++){ int nx = x + dx[i]; int ny = y + dy[i]; if (nx > 0 && ny > 0 && nx <= n && ny <= m && a[x][y] > a[nx][ny]){ f[x][y] = Math.max(f[x][y], dfs(nx, ny) + 1 ); } } return f[x][y]; } public static void main (String[] args) throws IOException { String[] str = br.readLine().split(" " ); n = Integer.parseInt(str[0 ]); m = Integer.parseInt(str[1 ]); for (int i = 1 ; i <= n; i++){ str = br.readLine().split(" " ); for (int j = 1 ; j <= m; j++){ a[i][j] = Integer.parseInt(str[j - 1 ]); } } int max = 0 ; for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= m; j++) max = Math.max(max, dfs(i, j)); System.out.println(max); } }