【luogu P4762】字符串合成 / Virus synthesis(PAM)(DP)

字符串合成 / Virus synthesis

题目链接:luogu P4762

题目大意

初始有一个空串,要你用最小花费构造给出的一个字符串。
你可以花费一个费用:
在字符串前或字符串后加一个字符,或者将这个字符串反转后得到的字符串接在这个字符串的前面或者后面。

思路

首先不难想到它反转再接其实就是构造出了一个回文串。
而且这个回文串一定是偶数长的回文串。

那回文串的一半里面又要有回文串,想到 PAM 里面的 trans 数组。
不难看到对于一个字符串,你就选一个回文串,然后两边的部分直接暴力搞,然后回文串就通过构造出一半,然后再翻转得到,那你考虑去 DP,设 fi 为构造出 PAM 里面的第 i 个点代表的回文串的最小费用。
那对于每个回文串,如果它作为一开始选的回文串,那就会有一个答案,是 nleni+fi,那我们就在这些里面选一个最小的就可以了。

那接着问题就是如何 DP 得到 fi
考虑分奇偶讨论:

  1. 奇数

那你应该就是在里面选一个偶数的回文串,然后剩下的暴力搞,但是 PAM 只支持后缀的回文串啊。
那你就考虑分两种转移,一种是当前的最后一个位置暴力搞,然后退回求 ffai,然后加上两边暴力搞的费用 2,要么是最后一个位置不暴力搞,那就是求 ffaili,然后剩下的部分暴力搞。
总的来说,就是 fi=min{ffai+2,ffaili+lenilenfaili}

  1. 偶数

那你就肯定是复制得到这个,很显然这个是最优的选择。
但是你不能保证你的一半是个回文串啊,你 DP 的状态都是回文串。
那你考虑像搞奇数一样搞,对于那一半,要么就是最边的位置用暴力搞,那反映在这里就是原来的串最外面的两个不要,费用就是 ffai+1。要么就是从这里开的字符串,然后前面覆盖不到的位置暴力搞,那就是 ftransi+leni/2lentransi+1。(加一是翻转的费用,然后因为是求小于等于原串二分之一的最长后缀回文串,所以用的是 transi
总的来说,就是 fi=min{ffai+1,ftransi+leni/2lentransi+1}

然后如果长度小于等于 2,那费用肯定就是原串长度,那直接特判掉就好了。
然后就好了。

代码

#include<cstdio> #include<cstring> #include<iostream> using namespace std; struct PAM { int fail, num, sum, trans; int son[26], len, fa; }t[100002]; int T, sn, tot, lst, f[100001], ans; char s[100001]; int get_new(int l) { t[tot].len = l; t[tot].fail = t[tot].num = t[tot].sum = t[tot].trans = 0; for (int i = 0; i < 26; i++) t[tot].son[i] = 0; tot++; return tot - 1; } int get_fail(int x, int pl) { while (s[pl - t[x].len - 1] != s[pl]) x = t[x].fail; return x; } void build_PAM() {//PAM tot = 0; get_new(0); get_new(0); t[1].fail = 0; t[0].fail = 1; t[0].len = 0; t[1].len = -1; lst = 0; for (int i = 1; i <= sn; i++) { // int pre = get_fail(lst, i), go = s[i] - 'a';//这个是 jzoj 的 int pre = get_fail(lst, i), go = s[i] - 'A';//这个是 luogu 的 if (!t[pre].son[go]) { int now = get_new(t[pre].len + 2); t[now].fail = t[get_fail(t[pre].fail, i)].son[go]; t[pre].son[go] = now; t[now].fa = pre; t[now].sum = t[pre].sum + 1; if (t[now].len <= 2) t[now].trans = t[now].fail; else { int tmp = t[pre].trans; while (s[i - t[tmp].len - 1] != s[i] || ((t[tmp].len + 2) << 1) > t[now].len) tmp = t[tmp].fail; t[now].trans = t[tmp].son[go]; } } lst = t[pre].son[go]; t[lst].num++; } } void count() { for (int i = tot - 1; i >= 2; i--) t[t[i].fail].num += t[i].num; } void DP() { ans = sn; f[0] = 0; f[1] = 0; for (int i = 2; i < tot; i++) {//长度小于等于 2 的特判 f[i] = t[i].len; if (t[i].len & 1 && t[i].len != 1) {//奇数长度 f[i] = min(f[i], f[t[i].fa] + 2); f[i] = min(f[i], f[t[i].fail] + t[i].len - t[t[i].fail].len); } else if (!(t[i].len & 1) && t[i].len != 2) {//偶数长度 f[i] = min(f[i], f[t[i].fa] + 1); f[i] = min(f[i], 1 + f[t[i].trans] + t[i].len / 2 - t[t[i].trans].len); } ans = min(ans, sn - t[i].len + f[i]); } } int main() { scanf("%d", &T); while (T--) { scanf("%s", s + 1); sn = strlen(s + 1); build_PAM(); DP(); printf("%d\n", ans); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_P4762.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(36)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示