序列 DP

一般序列 DP 核心思想:将序列的前 \(i\) 个数的状态用一个更简单的形式表示出,并且体现出这些状态对后续的影响。

题目

ABC 267D

给定一个序列 \(a\),找到一个长度为 \(m\) 的子序列 \(b\),使得 \(\sum b_i × i\) 最大。

\(n, m \le 2 × 10^3\)。

状态:\(f(i, j)\):前 \(i\) 个数,选 \(j\) 个数的最大和;

转移:\(f(i, j) = \max(f(i - 1, j), f(i - 1, j - 1) + a_i \times j)\)。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 2010;
  19. int n, m;
  20. ll a[N], dp[N][N];
  21. int main() {
  22. memset(dp, 128, sizeof dp);
  23. n = read(), m = read();
  24. for (int i = 1; i <= n; ++ i) {
  25. a[i] = read();
  26. dp[i][0] = 0;
  27. }
  28. dp[0][0] = 0;
  29. for (int i = 1; i <= n; ++ i) {
  30. for (int j = 1; j <= min(i, m); ++ j) {
  31. dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + 1ll * j * a[i]);
  32. }
  33. }
  34. printf("%lld\n", dp[n][m]);
  35. return 0;
  36. }

B3637 最长上升子序列

给定一个序列,求它的最长上升子序列。\(n \le 5000\)

状态:\(dp_i\):最长上升子序列中第 \(i\) 个元素(是什么);

转移:如果 新元素 \(a\) 大于 \(dp_i\),则 \(dp_{i + 1} = a\),否则,二分出第一个大于等于 \(a\) 的前一个位置,替换上它。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. int n;
  19. int y[100], dp[100];
  20. int main() {
  21. n = read();
  22. for (int i = 1; i <= n; ++ i) {
  23. y[i] = read();
  24. }
  25. dp[1] = y[1];
  26. int cnt = 1;
  27. for (int i = 2; i <= n; ++ i) {
  28. if (y[i] > dp[cnt]) {
  29. dp[++ cnt] = y[i];
  30. }
  31. else {
  32. int p = lower_bound(dp + 1, dp + cnt + 1, y[i]) - dp;
  33. dp[p] = y[i];
  34. }
  35. }
  36. printf("%d\n", cnt);
  37. return 0;
  38. }

P1439 【模板】最长公共子序列

给定两个 \(1, 2, 3 \cdots n\) 的序列 \(a, b\),求它们的最长公共子序列。

\(n \le 10^5\)。

  • 朴素做法 \(O_{n^2}\):

    状态:\(dp(i, j)\):第一个串的前 \(i\) 位,第二个串的前 \(j\) 位的 LCS 的长度;

    如果当前的 \(a_i = b_j\) 相同(即是有新的公共元素) 那么 dp[i][j]=max(dp[i][j],dp[i−1][j−1])+1;;如果不相同,即无法更新公共元素,考虑继承:dp[i][j]=max(dp[i−1][j],dp[i][j−1])
  1. #include<iostream>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 2010;
  19. int n, m;
  20. int dp[N][N], a1[N], a2[N];
  21. int main() {
  22. n = read(), m = read();
  23. for (int i = 1; i <= n; ++ i) {
  24. a1[i] = read();
  25. }
  26. for (int i = 1; i <= m; ++ i) {
  27. a2[i] = read();
  28. }
  29. for (int i = 1; i <= n; ++ i) {
  30. for (int j = 1; j <= m; ++ j) {
  31. dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
  32. if (a1[i] == a2[j]) {
  33. dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
  34. }
  35. }
  36. }
  37. printf("%d\n", dp[n][m]);
  38. return 0;
  39. }
  • 针对本题的 \(O_{n \log n}\) 做法。

    我们将 \(b\) 中的元素改为该元素在 \(a\) 中的位置,如果有一段位置是单调递增的,则这一段就是公共的子序列,我们再进行 DP。

    状态:\(dp_i\):最长公共子序列的第 \(i\) 个元素(是什么);

    转移:如果当前的 \(b_i\) 大于 \(dp_{last}\),则 \(dp_{last + 1} = b_i\),否则,二分找出大于等于 \(b_i\) 的前一个位置,替换。
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. const int N = 1e5 + 5;
  5. inline ll read() {
  6. ll x = 0;
  7. int fg = 0;
  8. char ch = getchar();
  9. while (ch < '0' || ch > '9') {
  10. fg |= (ch == '-');
  11. ch = getchar();
  12. }
  13. while (ch >= '0' && ch <= '9') {
  14. x = (x << 3) + (x << 1) + (ch ^ 48);
  15. ch = getchar();
  16. }
  17. return fg ? ~x + 1 : x;
  18. }
  19. int n;
  20. ll a[N], b[N], l[N], dp[N];
  21. int main() {
  22. n = read();
  23. for (int i = 1; i <= n; ++i) {
  24. a[i] = read();
  25. l[a[i]] = i;
  26. }
  27. for (int i = 1; i <= n; ++i) {
  28. b[i] = read();
  29. b[i] = l[b[i]];
  30. }
  31. int len = 1, s;
  32. dp[1] = b[1];
  33. for (int i = 2; i <= n; ++i) {
  34. if (b[i] > dp[len]) {
  35. len ++;
  36. dp[len] = b[i];
  37. }
  38. else {
  39. s = lower_bound(dp + 1, dp + len + 1, b[i]) - dp;
  40. dp[s] = b[i];
  41. }
  42. }
  43. printf("%d", len);
  44. return 0;
  45. }

区间 DP

区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。

令状态 \(f(i,j)\) 表示将下标位置 \(i\) 到 \(j\) 的所有元素合并能获得的价值的最大值,那么 \(f(i,j)=\max\{f(i,k)+f(k+1,j)+cost\}\),\(cost\) 为将这两组元素合并起来的代价。

题目

P1775 石子合并

有 \(n\) 堆石子,每堆石子有编号有重量,现在要将它们合并成一堆,只能合并相邻的两堆石子,且代价为两堆石子的重量和,求最小代价。\(n \le 300\)

状态:\(dp(i, j)\):合并 \(\left[ i, j \right]\) 的石子的最小代价。

转移:\(dp(i, j) = \min_{k = i}^{j - 1} \left\{ dp(i, k) + dp(k + 1, j) + cost(i, j) \right\}\)

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 410;
  19. int n;
  20. int a[N], dp[N][N], s[N];
  21. int main() {
  22. memset(dp, 0x3f, sizeof dp);
  23. n = read();
  24. for (int i = 1; i <= n; ++ i) {
  25. a[i] = read();
  26. dp[i][i] = 0;
  27. s[i] = s[i - 1] + a[i];
  28. }
  29. for (int l = 2; l <= n; ++ l) {
  30. for (int i = 1; i + l - 1 <= n; ++ i) {
  31. int j = i + l - 1;
  32. for (int k = i; k < j; ++ k) {
  33. dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);
  34. }
  35. }
  36. }
  37. printf("%d\n", dp[1][n]);
  38. return 0;
  39. }

P4170 [CQOI2007]涂色

有一条长度为 \(5\) 的木板,初始时没有涂过任何颜色。每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。用尽量少的涂色次数达到目标。\(n \le 50\)

状态:\(dp(i, j)\) 将木板 \(\left[ i, j \right]\) 涂成目标颜色的最小涂色次数。

转移:

如果 \(color_j = color_{j - 1}\),则 \(dp(i, j) = dp(i - 1, j)\);

否则 \(dp(i, j) = \min_{k = i}^{j - 1} \left \{ dp(i, k) + dp(k + 1, j) \right \}\)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 100;
  19. int n;
  20. char s[N];
  21. ll dp[N][N];
  22. int main() {
  23. scanf("%s", s + 1);
  24. n = strlen(s + 1);
  25. memset(dp, 0x3f, sizeof dp);
  26. for (int i = 1; i <= n; ++ i)
  27. dp[i][i] = 1;
  28. for (int l = 1; l < n; ++ l) {
  29. for (int i = 1; i + l <= n; ++ i) {
  30. int j = i + l;
  31. if (s[i] == s[j]) {
  32. dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]);
  33. } else {
  34. for (int k = i; k < j; ++ k)
  35. dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]);
  36. }
  37. }
  38. }
  39. printf("%lld\n", dp[1][n]);
  40. return 0;
  41. }

P1220 关路灯

在一条路线上安装了 \(n\) 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。现在已知老张走的速度为 \(1m/s\),每个路灯的位置(是一个整数,即距路线起点的距离,单位:m)、功率(W),老张关灯所用的时间很短而可以忽略不计。问:怎样最省电?\(n \le 50\)

考虑对于状态的设计,关掉一段区间的灯,需要存下左右端点,需要两维,对于关掉区间 \(\left [ l, r \right ]\),最后一次只可能是关掉 \(l\) 位置的灯或者 \(r\) 位置的灯,即最后停留的位置有最左端与最右端两种,且对答案有影响,再加一维来存这两种情况。

状态:\(dp(i, j, 0/1)\)

转移:\(dp(i, j, 0) = \min(dp(i + 1, j, 0) + cost_1, dp(i + 1, j, 1) + cost_2\)),

\(dp(i, j, 1) = \min(dp(i, j - 1, 0) + cost_1, dp(i, j - 1, 1) + cost_2)\),

初始化:\(dp(c, c, 0) = 0, dp(c, c, 1) = 0\)。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int f = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. f |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return f ? ~x + 1 : x;
  17. }
  18. const int N = 110;
  19. int n, c, sum, ans;
  20. int a[N], w[N], s[N];
  21. int dp[N][N][2];
  22. bool b[N];
  23. int main() {
  24. n = read(), c = read();
  25. for (int i = 1; i <= n; ++ i) {
  26. a[i] = read(), w[i] = read();
  27. s[i] = s[i - 1] + w[i];
  28. }
  29. memset(dp, 0x3f, sizeof dp);
  30. dp[c][c][0] = 0;
  31. dp[c][c][1] = 0;
  32. for (int j = c; j <= n; ++ j) {
  33. for (int i = j - 1; i >= 1; -- i) {
  34. dp[i][j][0] = min(dp[i + 1][j][0] + (a[i + 1] - a[i]) * (s[n] + s[i] - s[j]), dp[i + 1][j][1] + (a[j] - a[i]) * (s[n] + s[i] - s[j]));
  35. dp[i][j][1] = min(dp[i][j - 1][0] + (a[j] - a[i]) * (s[n] + s[i - 1] - s[j - 1]), dp[i][j - 1][1] + (a[j] - a[j - 1]) * (s[n] + s[i - 1] - s[j - 1]));
  36. }
  37. }
  38. ans = min(dp[1][n][0], dp[1][n][1]);
  39. printf("%d", ans);
  40. return 0;
  41. }

P3146 [USACO16OPEN]248 G

给定一个 \(1 \times n\) 的地图,在里面玩 2048,每次可以合并相邻两个(数值范围 \(1 \sim 40\)),问序列中出现的最大数字的值最大是多少。注意合并后的数值并非加倍而是 \(+1\),例如 \(2\) 与 \(2\) 合并后的数值为 \(3\),\(2 \le n \le 248\)

这里要考虑 2048 的游戏规则,即只有两个相邻的数相等才能合。

状态:\(dp(i, j)\):合并区间 \(\left [ i, j \right ]\) 后的最大数值。

转移:\(dp(i, j) = \max(dp(i, j), dp(k + 1, j) + 1)\),条件:\(dp(i, k) = dp(k + 1, j)\),这里要注意,如果 \(dp(k + 1, j)\) 为 \(0\),说明这段区间没被更新到,因此答案也不可能为 \(1\),应该为 \(0\)。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 510;
  19. int n, ans;
  20. int a[N];
  21. int dp[N][N];
  22. int main() {
  23. n = read();
  24. for (int i = 1; i <= n; ++ i) {
  25. a[i] = read();
  26. dp[i][i] = a[i];
  27. }
  28. for (int len = 1; len <= n - 1; ++ len)
  29. for (int i = 1; i <= n - len; ++ i) {
  30. int j = i + len;
  31. for (int k = i; k < j; ++ k) {
  32. if (dp[k + 1][j] > 0 && dp[i][k] == dp[k + 1][j]) {
  33. dp[i][j] = max(dp[i][j], dp[k + 1][j] + 1);
  34. ans = max(ans, dp[k + 1][j] + 1);
  35. }
  36. }
  37. }
  38. printf("%d", ans);
  39. return 0;
  40. }

P3205 [HNOI2010]合唱队

合唱队一共 \(n\) 个人,第 \(i\) 个人的身高为 \(h_i\) 米(\(1000 \le h_i \le 2000\)),并已知任何两个人的身高都不同。假定最终排出的队形是 \(A\) 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终棑排出的队形中: 第一个人直接插入空的当前队形中;对从第二个人开始的每个人,如果他比前面那个人高(\(h\) 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮(\(h\) 较小),那么将他插入当前队形的最左边。当 \(n\) 个人全部插入当前队形后便获得最终排出的队形。答案要对 \(19650827\) 取模,\(n \le 1000\),\(1000 \le h_i \le 2000\)。

这道题与关路灯那道题差不多,在状态设计上要考虑上一个被插入的人是插入的左边还是右边。

状态:\(dp(i, j, 0/1)\):区间 \(\left[ i, j \right]\) 中,最后一个人被插入了 \(0\) 左边 / \(1\) 右边。

转移:

如果 \(a_i < a_j\),那么 \(dp(i, j, 0) = dp(i, j, 0) + dp(i + 1, j, 1), dp(i, j, 1) = dp(i, j, 1) + dp(i, j - 1, 0)\),

如果 \(a_i < a_{i + 1}\),那么 \(dp(i, j, 0) = dp(i, j, 0) + dp(i + 1, j, 0)\),

如果 \(a_j > a_{j - 1}\),那么 \(dp(i, j, 1) = dp(i, j, 1) + dp(i, j - 1, 1)\)。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. const int N = 2010;
  19. const int mod = 19650827;
  20. int dp[N][N][2], a[N];
  21. int main() {
  22. int n;
  23. n = read();
  24. for (int i = 1; i <= n; ++ i)
  25. a[i] = read();
  26. for (int i = 1; i <= n; ++ i)
  27. dp[i][i][0] = 1;
  28. for (int i = n - 1; i >= 1; -- i) {
  29. for (int j = i + 1; j <= n; ++ j) {
  30. if (a[i] < a[j]) {
  31. dp[i][j][0] += dp[i + 1][j][1];
  32. dp[i][j][1] += dp[i][j - 1][0];
  33. dp[i][j][0] %= mod;
  34. dp[i][j][1] %= mod;
  35. }
  36. if (a[i] < a[i + 1]) {
  37. dp[i][j][0] += dp[i + 1][j][0];
  38. dp[i][j][0] %= mod;
  39. }
  40. if (a[j] > a[j - 1]) {
  41. dp[i][j][1] += dp[i][j - 1][1];
  42. dp[i][j][1] %= mod;
  43. }
  44. }
  45. }
  46. printf("%d", (dp[1][n][0] + dp[1][n][1]) % mod);
  47. return 0;
  48. }

环状 DP

在环上的 DP,基本方法就是断环为链,然后把它作为区间 DP 或 序列 DP 去做。作为区间 DP 做时有一个小技巧就是将 这个链复制两遍,以防止首位的一些非法情况被我们计算。

P1880 [NOI1995] 石子合并

石子合并,但是,是在环上。\(n \le 100\)

断环为链,复制两遍这条链转化为区间 DP。

状态:\(f1(i, j)\):区间 \(\left[ i, j \right ]\) 的最大得分,\(f2(i, j)\):区间 \(\left[ i, j \right ]\) 的最小得分。

转移:\(f1(i, j) = \max_{k = i}^{j - 1}\{f1(i, j), f1(i, k) + f1(k + 1, j) + cost\}\)

\(f2(i, j) = \min_{k = i}^{j - 1}\{f2(i, j), f2(i, k) + f2(k + 1, j) + cost\}\)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. ll r[600], g[600];
  19. ll f1[600][600], f2[600][600];
  20. int main() {
  21. int n;
  22. n = read();
  23. for (int i = 1; i <= n; ++i) {
  24. r[i] = read();
  25. r[i + n] = r[i];
  26. g[i] = g[i - 1] + r[i];
  27. f1[i][i] = f2[i][i] = 0;
  28. }
  29. for (int i = n + 1; i <= 2 * n; ++i) {
  30. f2[i][i] = 0;
  31. g[i] = g[i - 1] + r[i];
  32. }
  33. for (int l = 1; l < n; ++l) {
  34. for (int i = 1, j = i + l; i < n * 2 && j <= n * 2; ++i, j = i + l) {
  35. f2[i][j] = 100000000;
  36. for (int k = i; k < j; ++k) {
  37. f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + g[j] - g[i - 1]);
  38. f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + g[j] - g[i - 1]);
  39. }
  40. }
  41. }
  42. ll maxn = 0, minn = 1e18;
  43. for (int i = 1; i <= n; ++i) {
  44. maxn = max(maxn, f1[i][i + n - 1]);
  45. minn = min(minn, f2[i][i + n - 1]);
  46. }
  47. printf("%lld\n%lld", minn, maxn);
  48. return 0;
  49. }

P1063 [NOIP2006 提高组] 能量项链

给定一个有 \(n\) 个珠子的项链,每个珠子有头标记和尾标记,相邻两个珠子,前一个珠子的尾标记等于后一个珠子的头标记,只有相邻的两个柱子能合并成一个,并产生能量,能量为 \(前一颗珠子的头标记 \times 后一颗珠子的头标记 \times 后一颗珠子的尾标记\),合并后的新珠子,头标记等于前一颗珠子的头标记,尾标记等于后一颗珠子的尾标记。求最大能量。\(4 \le n \le 400\)

很经典的环形 DP 题。

状态:\(dp(i, j)\): 合并区间 \(\left[ i, j \right]\) 释放的最大能量。

转移:\(dp(i, j) = \max_{k = i}^{j - 1}\{dp(i, k) + dp(k + 1, j) + a_i \times a_{k + 1} \times a_{j + 1} \}\)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. inline ll read() {
  5. ll x = 0;
  6. int fg = 0;
  7. char ch = getchar();
  8. while (ch < '0' || ch > '9') {
  9. fg |= (ch == '-');
  10. ch = getchar();
  11. }
  12. while (ch >= '0' && ch <= '9') {
  13. x = (x << 3) + (x << 1) + (ch ^ 48);
  14. ch = getchar();
  15. }
  16. return fg ? ~x + 1 : x;
  17. }
  18. int n;
  19. ll a[300], f[300][300];
  20. ll maxn;
  21. int main() {
  22. n = read();
  23. for (int i = 1; i <= n; ++ i) {
  24. a[i] = read();
  25. a[i + n] = a[i];
  26. }
  27. for (int i = 2; i < 2 * n; ++ i)
  28. for (int j = i - 1; i - j < n && j >= 1; -- j)
  29. for (int k = j; k < i; ++ k) {
  30. f[j][i] = max(f[j][i], a[j] * a[k + 1] * a[i + 1] + f[j][k] + f[k + 1][i]);
  31. if (f[j][i] > maxn) maxn = f[j][i];
  32. }
  33. printf("%lld", maxn);
  34. return 0;
  35. }

「学习笔记」DP 学习笔记1的更多相关文章

  1. 「刷题笔记」DP优化-状压-EX

    棋盘 需要注意的几点: 题面编号都是从0开始的,所以第1行实际指的是中间那行 对\(2^{32}\)取模,其实就是\(unsigned\ int\),直接自然溢出啥事没有 棋子攻击范围不会旋转 首先, ...

  2. 「LibreOJ#516」DP 一般看规律

    首先对于序列上一点,它对答案的贡献只有与它的前驱和后驱(前提颜色相同)构成的点对, 于是想到用set维护每个颜色,修改操作就是将2个set暴力合并(小的向大的合并),每次插入时更新答案即可 颜色数要离 ...

  3. 「学习笔记」平衡树基础:Splay 和 Treap

    「学习笔记」平衡树基础:Splay 和 Treap 点击查看目录 目录 「学习笔记」平衡树基础:Splay 和 Treap 知识点 平衡树概述 Splay 旋转操作 Splay 操作 插入 \(x\) ...

  4. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  5. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  6. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  7. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  8. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  9. Note -「Lagrange 插值」学习笔记

    目录 问题引入 思考 Lagrange 插值法 插值过程 代码实现 实际应用 「洛谷 P4781」「模板」拉格朗日插值 「洛谷 P4463」calc 题意简述 数据规模 Solution Step 1 ...

  10. Note -「单位根反演」学习笔记

    \(\mathcal{Preface}\)   单位根反演,顾名思义就是用单位根变换一类式子的形式.有关单位根的基本概念可见我的这篇博客. \(\mathcal{Formula}\)   单位根反演的 ...

随机推荐

  1. Markdown/Latex常用数学公式语法

    0. 写在前面:MarkDown快捷键总结 名称 语法 快捷键 标题 用#号表示,#一级标题,##表示二级标题,依次类推 Ctrl+1.2.3.4 字体加粗 左右用**包裹起来 Ctrl+B 斜体字 ...

  2. Vue 路由导航守卫

    Vue 路由导航守卫 一:全局守卫 (1) router.beforeEach beforeEach((to, from, next) => {}) 接收三个参数,在路由切换成功之前调用 to ...

  3. 使用 Solon Cloud 的 Jaeger 做请求链路跟踪

    <dependency> <groupId>org.noear</groupId> <artifactId>jaeger-solon-cloud-plu ...

  4. 二进制安装Kubernetes(k8s) v1.23.3

    声明:微信公众号不支持富文本格式,代码缩进有问题 参考我其他平台文档: https://www.oiox.cn/index.php/archives/90/ https://juejin.cn/pos ...

  5. Redis为什么能抗住10万并发?揭秘性能优越的背后原因

    1. Redis简介 Redis是一个开源的,基于内存的,高性能的键值型数据库.它支持多种数据结构,包含五种基本类型 String(字符串).Hash(哈希).List(列表).Set(集合).Zse ...

  6. opencv基础

    Python 和 OpenCV 的结合是计算机视觉领域中应用最为广泛的一种方式,它们的结合使得开发者可以快速.高效地完成各种视觉任务.本文将介绍 Python 和 OpenCV 的基础使用,包括安装. ...

  7. 帝国cms 批量删除或者清空classurl(二级域名绑定)

      update phome_enewsclass set classurl= null ;

  8. async/await中的promise返回错误reject

    https://blog.csdn.net/qq_42543244/article/details/123423894 最近在学 node ,之前对 async/await 和 promise 略懂, ...

  9. P5356 [Ynoi2017] 由乃打扑克

    md调了5h才调出来恶心坏了没想到这么快就做了第二道Ynoi 据说这题其实不卡常 屠龙宝刀点击就送 题面也很清楚,给定两种操作,一种是区间加,一种是询问区间内第 k 小的数的值是多少. 对于区间加,在 ...

  10. python介绍、32位与64位系统的区别、python安装、pip管理安装包

    一.python的介绍 * python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为 ...