「REMAKE系列」线性dp篇

常见模型、技巧总结

LIS、LCS模型

LIS

LCS

有趣线性dp

  • 定义状态为修改中。 CF1155D

习题

洛谷——「能力综合提升题单-线性DP篇」

P2501 [HAOI2006]数字序列

省选/NOI- LIS、结论

现在我们有一个长度为 n 的整数序列 a。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。
第一行输出一个整数,表示最少需要改变多少个数。
第二行输出一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

  • 对于 100% 的数据,保证 1n3.5×1041ai105。数据保证 ai 随机生成。

思路

  • 结论题两个结论,数据随机 O(n2) 可以过。
  • 对于第一问,构造 b[i]=a[i]i,求 b[] 的最长单调不减子序列长度即可。
  • 对于第二问。考虑 b[] 中最长单调不减子序列中的相邻元素下标为 i, j,对应下标之间的大小一定不在 [b[i], b[j]] 之间,否则子序列可以更长。
    • 那么对于每一对相邻元素(序列不止一个),最终的结果一定是中间某一个 k(ikj), 使得 b[i..k] = b[i], b[k+1...j]=b[j],这样的结果是最优的。
    • 所以对于每一对相邻元素的区间,采用 dp 转移,设 dp[i] 表示处理以下标 i 为结尾的最长单调不减子序列最小代价
    • 枚举分界点 k。判断相邻元素区间合法,在 LIS 时记录每个下标作为末尾对应的最长长度。
const int N = 4e4 + 10; int a[N], n, b[N], v[N]; ll dp[N]; ll s[N], suf[N], L[N]; vector<int> pos[N]; int main() { re(n); for (int i = 1; i <= n; i++) re(a[i]), b[i] = a[i] - i; b[0] = -2e9; b[n + 1] = 2e9; // 最后一个(段)元素也是会被修改的,所以将边界搞到 n + 1 int len = 0; for (int i = 1; i <= n + 1; i++) { int l = 0, r = len + 1; while (l < r) { int mid = (l + r) >> 1; if (b[v[mid]] > b[i]) r = mid; else l = mid + 1; } if (l == len + 1) v[++len] = i; else v[l] = i; L[i] = l; pos[l].pb(i); } int ans1 = n - len + 1; memset(dp, 0x3f, sizeof dp); dp[0] = 0; pos[0].pb(0); for (int i = 1; i <= n + 1; i++) { for (auto pre: pos[L[i] - 1]) { if (pre > i || b[pre] > b[i]) continue; s[pre] = 0; suf[i - 1] = 0; for (int j = pre + 1; j <= i - 1; j++) { s[j] = s[j - 1] + abs(b[j] - b[pre]); } for (int j = i - 2; j >= pre; j--) suf[j] = suf[j + 1] + abs(b[j + 1] - b[i]); for (int k = pre; k <= i - 1; k++) { // pre, k --- k + 1, i dp[i] = min(dp[i], dp[pre] + s[k] + suf[k]); } } } printf("%lld\n%lld\n", ans1, dp[n + 1]); return 0; }

Codeforces

CF1155D. Beautiful Array

*1900 定义状态为修改中

定义三个状态,未修改、正在修改、修改结束了,相互转移即可

const int N = 3e5 + 10; ll f[N][3], s[N]; ll a[N]; int main() { ll n, x; re(n), re(x); ll ans = 0; for (int i = 1; i <= n; i++) { re(a[i]); f[i][0] = max(f[i - 1][0] + a[i], max(a[i], 0ll)); f[i][1] = max(max(f[i - 1][0], f[i - 1][1]) + x * a[i], max(0ll, x * a[i])); f[i][2] = max(max({f[i - 1][2], f[i - 1][1], f[i - 1][0]}) + a[i], max(0ll, a[i])); ans = max({ans, f[i][0], f[i][1], f[i][2]}); } printf("%lld\n", ans); return 0; }

CF1446B. Catching Cheaters

*1800 LCS变形

  • LCS变形,将状态转移代价改为对答案的贡献
const int N = 5010; int dp[N][N]; int main() { int n, m; IOS; cin >> n >> m; string a, b; cin >> a >> b; a = " " + a, b = " " + b; int ans = 0; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) - 1; dp[i][j] = max(0, dp[i][j]); if (a[i] == b[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 2); ans = max(ans, dp[i][j]); } } cout << ans << endl; return 0; }

CF666A. Reberland Linguistics

*1800

已知有长度为n的由小写字母构成的一个字符串, 现在对字符串进行一些操作

  • 选择一个长度\color{red}\text{大于} 4的“根”字符串
  • 然后在“根”字符串之后连接上任意个长度为2或3的“后缀”字符串。
    • 唯一的限制条件是,不可以连续接上两个相同的“后缀”字符串。

现在求构造过程中,有可能被用作“后缀”的字符串共有多少种。P.S:多个同样的“后缀”字符串只算一种。

思路

  • dp[i][j] i 开始接 j 个字符串是合法。
  • 从后往前做dp,转移条件是后面相同长度的串 不等且合法 或者 不同长度串合法
int dp[10010][4]; // dp[i][j] i 开始接 j 个字符串是合法。 int main() { IOS; string s; cin >> s; s = " " + s; int n = s.size() - 1; set<string> res; if (n <= 8) { if (n <= 6) { cout << 0 << endl; return 0; } else if (n == 7) { res.insert(s.substr(6)); } else { res.insert(s.substr(6)); res.insert(s.substr(7)); } cout << SZ(res) << endl; for (auto t: res) cout << t << endl; return 0; } dp[n - 1][2] = 1, dp[n - 2][3] = 1; res.insert(s.substr(n - 1)); res.insert(s.substr(n - 2)); for (int i = n - 3; i >= 6; i--) { for (int j = 2; j <= 3; j++) { if (n - (i + j) + 1 < 2) continue; string a = s.substr(i, j), b = s.substr(i + j, j); if (dp[i + j][5 - j] || (a != b && dp[i + j][j])) dp[i][j] = 1; if (dp[i][j]) res.insert(a); } } cout << SZ(res) << endl; for (auto t: res) cout << t << endl; return 0; }

CF682D. Alyona and Strings

*1900LCS变形

  • dp[i][j][k] 表示为 a 串前 i 位与 b 串前 j 位匹配 k 次的最大长度
  • 转移简单,但每次转移完需要更新为前缀最大值
int n, m, K; const int N = 1010; int dp[N][N][11]; char a[N], b[N]; int main() { re(n), re(m), re(K); scanf("%s", a + 1); scanf("%s", b + 1); int ans = 0; for (int k = 1; k <= K; k++) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (a[i] == b[j]) { dp[i][j][k] = max(dp[i - 1][j - 1][k] + 1, dp[i - 1][j - 1][k - 1] + 1); } ans = max(ans, dp[i][j][k]); } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) dp[i][j][k] = max(dp[i][j][k], max(dp[i - 1][j][k], dp[i][j - 1][k])); } } printf("%d\n", ans); return 0; }

CF1183H. Subsequences (hard version)

*1900

  • 设状态为 dp[i][j] 前 i 个字符,序列长度为 j 的方案数。
  • 转移:dp[i][j]=dp[i1][j]+(dp[i1][j1]dp[pre[i]1][j1])
const int N = 1010; ll dp[N][N], n, k, pos[26], pre[N]; char s[N]; int main() { re(n), re(k); scanf("%s", s + 1); for (int i = 1; i <= n; i++) { pre[i] = pos[(s[i] - 'a')]; pos[(s[i] - 'a')] = i; } dp[0][0] = 1; for (int i = 1; i <= n; i++) { dp[i][0] = 1; for (int j = 1; j <= i; j++) { dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]; if (pre[i]) dp[i][j] -= dp[pre[i] - 1][j - 1]; // printf("dp[%d][%d]=%d\n", i,j,dp[i][j]); } } ll sum = 0; for (int j = n; j >= 0 && k > 0; j--) { ll cnt = dp[n][j]; if (k >= cnt) { k -= cnt; sum += cnt * 1ll * (n - j); } else { sum += k * 1ll * (n - j); k = 0; } } if (k) puts("-1"); else printf("%lld\n", sum); return 0; }

__EOF__

本文作者Roshin
本文链接https://www.cnblogs.com/Roshin/p/remake_linear_dp.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Roshin  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
-->
点击右上角即可分享
微信分享提示