好难啊。。根本不会做。。基本上是抄Claris。。。

题目链接: (bzoj)https://www.lydsy.com/JudgeOnline/problem.php?id=4044

(luogu)https://www.luogu.org/problemnew/show/P4762

题解: 先观察到三个(ju)性(fei)质(hua): 2操作的结果一定是一个回文串(废话), 最后一个2操作之后只能进行1操作(废话), 只进行1操作花费代价等于字符个数(废话)

三句废话连在一起说: 最后一次2操作产生了一个回文串,此后只能进行1操作,总代价等于回文串代价+目标串长度-回文串长度

又因为一个串的本质不同回文子串个数是\(O(n)\)个,而且奇数长度的回文串没有任何意义,所以实际上有用的状态只是这个串的那些偶数长度的回文子串。。。

然后就可以在回文自动机上DP

考虑如何生成一个偶数长度的回文串,最优方案最后一步肯定是2操作(废话),那么考虑最后2操作之前的一步

因为我们只记录回文串的状态,所以我们希望建立从回文串到回文串的转移,而不能依赖于其他子串。

第一种情况,2操作之前的最后一步将一个字符加在了外面,则代价为该串去掉两头字符后的回文串代价+1 (不需要考虑更多,因为回文串去掉两头还是回文串)

第二种情况,2操作之前的最后一步将字符加在了里面,于是只能找到该串右半侧的最长回文后缀(或左半侧的最长回文前缀)与之建立联系,则代价为(该串串长的一半-右半侧最长回文后缀长度)+右半侧最长回文后缀代价+1 (最后一个+1是指要复制一次,但是第一种情况的+1指的是在复制之前加了一个字符,最后一步复制的代价在去掉两头字符的代价中算过了)

要注意初始值是\(f[0]=1\), 因为要求的是长度为0的回文串的代价,相当于自我复制一遍,以供第一种情况转移。

最后的问题是,如何对于一个串的一个回文子串快速求出不超过其长度一半的最长回文后缀?

建回文自动机,在上面进行递推

一开始觉得是从它的fail递推下来十分直观(因为fail本来就是最长回文后缀),但是实际上这样并不好(可能需要建出“fail树”再在上面做一些树的操作)。

比较好的方法应该是对于当前要递推的\(u\)点找到点\(p\)满足\(son[p][ch]=u\) (\(ch\)是\(u\)的结束字符,\(p\)点在构建回文自动机的过程中已经找到了),然后从\(p\)开始跳fail,直到合法为止,然后再走到它的\(ch\)儿子。因为这样可以避免在fail树上自上而下寻找,而采用更方便的\(son\)来向下寻找。

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std; const int N = 1e5+2;
const int S = 4;
char a[N+3];
int len[N+3];
int fail[N+3];
int son[N+3][S+1];
int bd[N+3];
int dp[N+3];
int que[N+3];
int n,siz,lstpos; int decode(char ch)
{
if(ch=='A') return 1;
else if(ch=='C') return 2;
else if(ch=='T') return 3;
else if(ch=='G') return 4;
} void initPAM()
{
siz = 1; fail[0] = fail[1] = 1; len[0] = 0; len[1] = -1; lstpos = 1; bd[0] = 0; bd[1] = 1;
} void insertchar(int id)
{
// printf("insert %d\n",a[id]);
int p = lstpos;
while(a[id-1-len[p]]!=a[id]) {p = fail[p];}
if(!son[p][a[id]])
{
siz++; int u = siz,v = fail[p];
while(a[id-1-len[v]]!=a[id]) {v = fail[v];}
fail[u] = son[v][a[id]]; len[u] = len[p]+2; son[p][a[id]] = u;
// printf("p=%d u=%d\n",p,u);
if(len[u]<=2) {bd[u] = fail[u];}
else
{
bd[u] = bd[p];
while(a[id-1-len[bd[u]]]!=a[id] || (len[bd[u]]+2)*2>len[u])
{
bd[u] = fail[bd[u]];
}
bd[u] = son[bd[u]][a[id]];
}
}
lstpos = son[p][a[id]];
} void clear()
{
for(int i=0; i<=siz; i++) bd[i] = dp[i] = len[i] = fail[i] = son[i][1] = son[i][2] = son[i][3] = son[i][4] = 0;
} int main()
{
int T; scanf("%d",&T);
while(T--)
{
initPAM();
scanf("%s",a+1); n = strlen(a+1);
for(int i=1; i<=n; i++) a[i] = decode(a[i]);
for(int i=1; i<=n; i++)
{
insertchar(i);
}
// printf("siz=%d\n",siz);
// for(int i=0; i<=siz; i++) for(int j=1; j<=S; j++) {if(son[i][j]) printf("son%d %d %d\n",i,j,son[i][j]);}
// printf("fail: "); for(int i=0; i<=siz; i++) printf("%d ",fail[i]); puts("");
// printf("bd: "); for(int i=0; i<=siz; i++) printf("%d ",bd[i]); puts("");
for(int i=1; i<=siz; i++) {if(len[i]&1) dp[i] = len[i];}
int head = 1,tail = 1;
que[1] = 0; dp[0] = 1;
while(head<=tail)
{
int u = que[head]; head++;
for(int i=1; i<=S; i++)
{
if(son[u][i])
{
tail++; que[tail] = son[u][i];
dp[son[u][i]] = min(dp[u]+1,((len[son[u][i]])>>1)-len[bd[son[u][i]]]+dp[bd[son[u][i]]]+1);
}
}
}
int ans = n;
for(int i=0; i<=siz; i++) ans = min(ans,n-len[i]+dp[i]);
printf("%d\n",ans);
clear();
}
return 0;
}

BZOJ 4044 Luogu P4762 [CERC2014]Virus Synthesis (回文自动机、DP)的更多相关文章

  1. luogu P4762 [CERC2014]Virus synthesis (回文自动机)

    大意: 初始有一个空串, 操作(1)在开头或末尾添加一个字符. 操作(2)在开头或末尾添加该串的逆串. 求得到串$S$所需最少操作数. 显然最后一定是由某个偶回文通过添加字符得到的, 那么只需要求出所 ...

  2. bzoj4044/luoguP4762 [Cerc2014]Virus synthesis(回文自动机+dp)

    bzoj4044/luoguP4762 [Cerc2014]Virus synthesis(回文自动机+dp) bzoj Luogu 你要用ATGC四个字母用两种操作拼出给定的串: 1.将其中一个字符 ...

  3. BZOJ 4044 Virus synthesis (回文自动机+dp)

    题目大意: 你可以在一个串的开头或者末尾加入一个字符,或者把当前整个串$reverse$,然后接在前面或者后面,求达到目标串需要的最少操作次数 对目标串建出$PAM$ 定义$dp[x]$表示当前在回文 ...

  4. bzoj 4044: Virus synthesis 回文自动机

    题目大意: 你要用ATGC四个字母用两种操作拼出给定的串: 将其中一个字符放在已有串开头或者结尾 将已有串复制,然后reverse,再接在已有串的头部或者尾部 一开始已有串为空.求最少操作次数. le ...

  5. [BZOJ4044]Virus synthesis 回文自动机的DP

    4044: [Cerc2014] Virus synthesis Time Limit: 20 Sec  Memory Limit: 128 MB Description Viruses are us ...

  6. bzoj 4044 Virus synthesis - 回文自动机 - 动态规划

    题目传送门 需要高级权限的传送门 题目大意 要求用两种操作拼出一个长度为$n$的只包含'A','T','G','C'的字符串 在当前字符串头或字符串结尾添加一个字符 将当前字符串复制,将复制的串翻转, ...

  7. bzoj2084/luoguP3501 [Poi2010]Antisymmetry(回文自动机+dp)

    bzoj2084/luoguP3501 [Poi2010]Antisymmetry(回文自动机+dp) bzoj Luogu 对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一 ...

  8. bzoj 4044: [Cerc2014] Virus synthesis【回文自动机+dp】

    建回文自动机,注意到一个回文串是可以通过一个长度小于等于这个串长度的一半的回文串添上一些字符然后复制得到的,也就是在自动机上向fa走,相当于treedp 每次都走显然会T,记录一个up,指向祖先中最下 ...

  9. 洛谷P4762 [CERC2014]Virus synthesis(回文自动机+dp)

    传送门 回文自动机的好题啊 先建一个回文自动机,然后记$dp[i]$表示转移到$i$节点代表的回文串的最少的需要次数 首先肯定2操作越多越好,经过2操作之后的串必定是一个回文串,所以最后的答案肯定是由 ...

随机推荐

  1. 自动生成Makefile的全过程详解

    一.简介 Linux下的程序开发人员,一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便.一般情况下,大家都是手工写一个简单Makefile,如果要想写出一个符合自由软件惯例的 ...

  2. E20170629-hm

    enqueue  [计] 入队,排队; dequeue  [计]  出列; rear  n. 后部,背面,背后; 臀部; (舰队或军队的) 后方,后尾,殿后部队; 〈英口〉厕所; ring buffe ...

  3. SpringBoot集成Redis来实现缓存技术方案

    概述 在我们的日常项目开发过程中缓存是无处不在的,因为它可以极大的提高系统的访问速度,关于缓存的框架也种类繁多,今天主要介绍的是使用现在非常流行的NoSQL数据库(Redis)来实现我们的缓存需求. ...

  4. [Swift通天遁地]七、数据与安全-(3)Cocopods的安装和开源类库对JSON的解析

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  5. Codeforces 903G 巧妙的线段树

    思路: 巧妙的线段树 想方法将网络流往数据结构方向转化 http://www.cnblogs.com/yyf0309/p/8724558.html //By SiriusRen #include &l ...

  6. [转]我是蒟蒻,但我有我的OI信仰

    我想最大的浪漫莫过于有人陪你征战OI吧 有多少无眠的夜晚?我总是在想, 到底是为了什么? 为了自招?为了省队?为了签约? 这条路很艰难,不可谓不凶险, 当你第一次踏上复试, 你肯定有看到过那些很厉害很 ...

  7. 【Leetcode146】LRU Cache

    问题描述: 设计一个LRU Cache . LRU cache 有两个操作函数. 1.get(key). 返回cache 中的key对应的 val 值: 2.set(key, value). 用伪代码 ...

  8. java.net.URISyntaxException: Illegal character in query

    java使用httpclient爬取一个网站的时候,请求:String url3="http://sh.58.com/ershoufang/33562546149042x.shtml?amp ...

  9. c# winform控件dock属性停造位置、摆放顺序详解

    dock : [英文释义- 码头.依靠][winform释义- 获取或设置当前控件依靠到父容器的哪一个边缘.] 用途:多数控件都有这个属性,主要用来设置控件的布局. 但对于不太了解这个属性的朋友来说有 ...

  10. Spring Cloud (6) 分布式配置中心-高可用

    高可用 现在已经可以从配置中心读取配置文件了,当微服务很多时都从配置中心读取配置文件,这时可以将配置中心做成一个微服务,将其集群化,从而达到高可用. 改造config-server 加入eureka ...