bzoj 4044: Virus synthesis 回文自动机

题目大意:

你要用ATGC四个字母用两种操作拼出给定的串:

  1. 将其中一个字符放在已有串开头或者结尾
  2. 将已有串复制,然后reverse,再接在已有串的头部或者尾部
    一开始已有串为空。求最少操作次数。
    len<=100000

题解:

我们一看 ! 这道题跟回文自动机没有半毛钱关系啊 !!
仔细分析一下第二个操作,我们发现这个操作过后实际上就产生了一个大回文串
所以我们可以考虑如何在回文自动机上搞

一开始想歪了,一直倒着想:如何把给定字符串消掉,然后一直没有想出来


其实这道题应该正着想
\(f_i\)表示达到回文自动机中的第\(i\)个节点所需要的最小操作.
那么我们有:
对于所有的通过添加一个字符可以转移到\(i\)\(f_j\)
\(f_i = min\{f_j + 1\}\)即在得到\(j\)倍增之前添加一个字符于是转移到i
注意 : 最优化下最后一步一定是倍增
我们还有\(f_i = min\{ \frac{len_i}{2} + f_{fail_i} - len_{fail_i} + 1\}\)
就是考虑这个串由所有回文后缀转移过来.
但是这样转移其实是\(O(n^2)\)的,我们应到考虑优化
其实我们发现:如果跳fail指针,那么\(f_{fail_i}\)会变小,但是相应的另一部分会变大
由反证法可得,一定不会出现决策时选择\(fail_i\)\(fail_{fail_i}\)更优的情况
所以我们只需要考虑取到的第一个可行决策即可.
但是这样还是不够:可行决策的两个条件:

  • s[len-T[x].len-1] == s[len]
  • (T[x].len+2)*2 <= T[cur].len

找到第一个可行决策的时间过长,依然会TLE
所以我们考虑记录一下每个节点的第一个可行决策
在找最优决策的时候,我们跳转last的可行决策,然后跳fail继续查找即可
不难发现这样一定是对的:

证明:
我们设当前新建状态为\(cur\)那么当我们利用第二个dp方程转移时一定有\(T[cur].len < T[last].len\)
所以对于\(last\)不成立的状态即\((T[x].len+2)*2 > T[last].len\)
一定也有\((T[x].len+2)*2 > T[last].len\)
故一定可以直接从\(last\)的可行决策点向前取.

所以我们在正常地找出第一个可以拓展成目前最长回文后缀的状态
对着这个状态的决策点一直向前跳即可.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(int &x){
	x=0;char ch;bool flag = false;
	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
const int maxn = 100010;
inline int idx(char ch){
	if(ch == 'A') return 0;
	if(ch == 'T') return 1;
	if(ch == 'C') return 2;
	if(ch == 'G') return 3;
	return 0;
}
struct Node{
	int nx[4];
	int fail,len,p;
	void clear(){
		nx[0] = nx[1] = nx[2] = nx[3] = 0;
		fail = len = p = 0;
	}
}T[maxn];
int last,nodecnt,len,s[maxn];
inline void init(){
	last = nodecnt = 0;
	T[0].clear();T[1].clear();
	T[++nodecnt].len = -1;
	T[0].fail = 1;
	s[len=0] = -1;
}
inline void insert(int c){
	s[++len] = c;int cur,p,x;
	for(p = last;s[len-T[p].len-1] != s[len];p = T[p].fail);
	if(T[p].nx[c] == 0){
		T[nodecnt+1].clear();T[cur = ++nodecnt].len = T[p].len + 2;
		for(x = T[p].fail;s[len-T[x].len-1] != s[len];x = T[x].fail);
		T[cur].fail = T[x].nx[c];
		T[p].nx[c] = cur;
		if(T[cur].len <= 2) T[cur].p = T[cur].fail;
		else{
			for(x = T[p].p;s[len-T[x].len-1] != s[len] || (T[x].len+2)*2 > T[cur].len;x = T[x].fail);
			T[cur].p = T[x].nx[c];
		}
	}last = T[p].nx[c];
}
char str[maxn];
int q[maxn],l,r,f[maxn];
int main(){
	int n;read(n);
	while(n--){
		init();scanf("%s",str);
		int len = strlen(str);
		for(int i=0;i<len;++i) insert(idx(str[i]));
		for(int i=2;i<=nodecnt;++i){
			if(T[i].len&1) f[i] = T[i].len;
		}

		l = 0;r = -1;q[++r] = 0;f[0] = 1;
		int ans = len;
		while(l <= r){
			int u = q[l++],v;
			for(int i=0;i<4;++i){
				v = T[u].nx[i];
				if(v == 0) continue;
				f[v] = min(f[u] + 1,T[v].len/2 + f[T[v].p] - T[T[v].p].len + 1);
				ans = min(ans,f[v] + (len - T[v].len));
				q[++r] = v;
			}
		}printf("%d\n",ans);
	}
	getchar();getchar();
	return 0;
}
posted @ 2017-03-11 07:35  Sky_miner  阅读(724)  评论(0编辑  收藏  举报