当前位置: 首页 > news >正文

ARC058D 笔记

感觉不是特别困难(?),思路歪了没想出来。

主要是想后缀转移,实际上这个想法很蠢,因为这样相比前缀转移很难做字典序的比较,而且不存在对状态剪枝的好性质了。

字典序题最好前缀后缀都想一遍吧。。。

题意

给定 \(N\) 个字符串 \(S_1, \ldots, S_N\) 和一个整数 \(K\),你需要选择一个子序列 \(i_1, i_2, \ldots, i_m\),使得:

  • \(i_1 < i_2 < \ldots < i_m\)
  • \(T = S_{i_1} + S_{i_2} + \ldots + S_{i_m}\)\(|T| = K\)
  • 在所有可能的方案中,\(T\) 字典序最小。

输出 \(T\)

\(1 \le N \le 2000, 1 \le K \le 10^4\)

思路

首先有一个简单的暴力,设 \(dp(i, j)\) 表示前 \(i\) 个字符串总长为 \(j\),字典序最小的串是什么。总时间复杂度 \(O(NK^2)\),因为字符串比较是 \(O(K)\) 的。

考虑优化,首先因为状态总空间是 \(O(NK^2)\) 的,要尝试剪掉多余的状态。第一个观察就是,如果 \(i + 1 \sim N\) 凑不出来 \(K - j\) 的长度,那 \(dp(i, j)\) 肯定没用。但这个观察不够强。第二个观察就是,在有用的 \(j\) 中,如果 \(dp(i, j)\)\(dp(i, k)\) 存在某一位不同,那字典序更大的就没用了。这个观察是很强的,它给的性质就是任意有用的 \(j, k\),假设 \(j < k\),一定满足 \(dp(i, j)\)\(dp(i, k)\) 的前缀。

那就可以对每个 \(i\) 维护一个母串 \(T_i\),每个 \(dp(i, j)\) 都是 \(T_i\) 的一个前缀。接下来考虑转移,假设转移 \(dp(i, j)\),它的取值为 \(\min\{dp(i - 1, j), dp(i - 1, j - |S_i|) + S_i\}\),如果 \(dp(i - 1, j)\)\(dp(i - 1, j - |S_i|)\) 不是 \(T_{i - 1}\) 的前缀,直接取是 \(T_{i - 1}\) 前缀的那个就行了。否则,就相当于在比较 \(T_{i - 1}\) 的某个子串与 \(S_i\) 的字典序大小,可以 Z 函数或哈希求。

接着考虑求出 \(T_i\),由于空间不够,我们只能求出 \(dp(i, j)\) 的选择。从小到大枚举 \(j\),用一个栈更新 \(T_i\),从栈顶到栈底分别是有用的递减的 \(j\)。每次加入一个 \(dp(i, j)\),如果 \(dp(i, j) > dp(i, top)\),则跳过 \(j\),否则一直弹出栈顶使得 \(dp(i, top)\)\(dp(i, j)\) 的前缀,接着往栈中压入 \(j\) 即可。

如何比较呢?比较一定形如 \(S_i\) 的后缀与 \(S_i\) 的前缀比较,或 \(S_i\)\(T_i\) 的某个字串比较,用 Z 函数或哈希同样可以解决。

时间复杂度 \(O(NK + \sum |S_i|)\)

字符串题细节好多(悲),代码写了依托:

#include <bits/stdc++.h>using i64 = long long;
using namespace std;constexpr int N = 2E3 + 5, K = 1E4 + 5;int n, k, len[N];
string s[N];
string t[N];bitset<K> suf[N];int dp[N][K], z[K * 2];void getz(int n, const string &s) {for (int i = 2, l = 1, r = 1; i <= n; ++i) {if (i < r && z[i - l + 1] < r - i) {z[i] = z[i - l + 1];} else {z[i] = max(0, r - i);for (; i + z[i] <= n && s[i + z[i]] == s[z[i] + 1]; ++z[i]);}if (i + z[i] > r) {l = i;r = i + z[i];}}
}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);cin >> n >> k;for (int i = 1; i <= n; ++i) {cin >> s[i];len[i] = s[i].size();}suf[n + 1].set(0);for (int i = n; i >= 1; --i) {suf[i] = suf[i + 1] | (suf[i + 1] << len[i]);}t[0] = "";for (int j = 1; j <= k; ++j) {dp[0][j] = -1;}for (int i = 1; i <= n; ++i) {string P = s[i] + '|' + t[i - 1];int L = P.size();P = '#' + P;getz(L, P);stack<int> stk;for (int j = 0; j <= k; ++j) {if (!suf[i + 1][k - j] || j - len[i] > (int)t[i - 1].size()) {dp[i][j] = -1;continue;}auto comp = [&](int p) {int lcp = z[len[i] + 2 + p];if (lcp == len[i]) {return 0;}if (p + lcp == t[i - 1].size()) {return 1;}return (t[i - 1][p + lcp] < s[i][lcp] ? -1 : 1);};if (j < len[i]) {if (dp[i - 1][j] == -1) dp[i][j] = -1;else dp[i][j] = 1;} else {if (dp[i - 1][j] == -1 && dp[i - 1][j - len[i]] == -1) {dp[i][j] = -1;continue;}if (dp[i - 1][j] == -1) {dp[i][j] = (comp(j - len[i]) != -1 ? 2 : -1);} else if (dp[i - 1][j - len[i]] == -1) {dp[i][j] = 1;} else {if (comp(j - len[i]) != 1) {dp[i][j] = 1;} else {dp[i][j] = 2;}}}if (dp[i][j] == -1) continue;while (!stk.empty()) {int top = stk.top();if (dp[i][top] == 1) {if (dp[i][j] == 1 || j - len[i] >= top) {stk.push(j);break;}int lcp = z[j + 2];if (lcp < top + len[i] - j) {dp[i][top] = -1;stk.pop();} else {stk.push(j);break;}} else {int plcp = z[top + 2];if (dp[i][j] == 1 || top + plcp <= j) {if (plcp == len[i]) {stk.push(j);} else {dp[i][j] = -1;}break;}int c = top + len[i] - j;int lcp = z[len[i] - c + 1];if (lcp == c) {stk.push(j);break;}if (s[i][len[i] - c + lcp] > s[i][lcp]) {dp[i][top] = -1;stk.pop();} else {dp[i][j] = -1;break;}}}if (stk.empty()) {stk.push(j);}}assert(!stk.empty());int top = stk.top();if (dp[i][top] == 1) {t[i] = t[i - 1].substr(0, top);} else {t[i] = t[i - 1].substr(0, top - len[i]) + s[i];}for (int i = 1; i <= P.size(); ++i) z[i] = 0;}assert(t[n].size() == k);cout << t[n] << "\n";return 0;
}
http://www.rkmt.cn/news/874.html

相关文章:

  • SSE技术总结
  • OCP、OMSP 和 OLP 是三种常见的光层保护机制的对比
  • 自从切到Qoder开发后,每天都心旷神怡
  • 电子烟的4种屏幕驱动集成语音方案介绍
  • Altair PSIM 2024下载地址与安装教程
  • CF643E Bear and Destroying Subtrees
  • Go语言系统信息获取示例
  • OpenCSG 哈投达成战略合作,加速东北企业AI转型
  • 收录笔记:蜘蛛池,蜘蛛池出租 - 蚂蚁站群
  • 核心漏洞开发实战:Win32漏洞挖掘与防护绕过深度解析
  • Karmada v1.15 版本发布!多模板工作负载资源感知能力增强
  • 使用JavaScript开发谷歌浏览器插件:实现与核心要点
  • 自动化SEO工具:黑帽站群软件 - 蚂蚁站群
  • openssl编程之hmac算法编程示例
  • c#项目迁移至Kubernetes之NTLM认证问题解决方案
  • AI写代码
  • 蚂蚁超级镜像站群搜索:多站搭建教程,提升排名实战手记 - 蚂蚁站群
  • 易基因:安医大陈飞虎团队揭示METTL3介导m6A甲基化在炎症性疾病发病机制中的表观调控作用:IJBM|项目文章
  • 一键批量镜像站群的软件,多任务不费时 - 蚂蚁站群
  • Year of the Rabbit – TryHackMe
  • 20231313张景云《密码系统设计》第一周
  • LLM-RAG项目细节-数据处理、分块..
  • 我的多站点管理神器:超级镜像站群使用手记 - 蚂蚁站群
  • CF2127H 23 Rises Again
  • 为什么收集分析用户反馈比功能上线更重要?
  • Symfony学习笔记 - Symfony Documentation - The Basics(2)
  • 【分享+1】HarmonyOS官方模板优秀案例(第6期:商务办公 笔记应用)
  • TypeScript 队列实战:从零实现简单、循环、双端、优先队列,附完整测试代码
  • 半导体行业CRM就用八骏CRM
  • c++开发大模型mcp服务(七)使用cpp-mcp的例子MCP-ExcelAutoCpp