【CERC2014】【BZOJ4044】Virus synthesis(回文自动机)

传送门


题解:

很显然按照这个构造方式,我们一定是在构造出某一个偶数长度的回文串之后把剩下补齐。

可以很显然地发现奇数回文串没有作为最终串的价值,但是我们可以构造奇数串,复制成偶数串。

m n [ u ] mn[u] mn[u]表示构造出串 u u u需要的最小次数,对于奇数串我们有 m n [ u ] = l e n [ u ] mn[u]=len[u] mn[u]=len[u],对于偶数串,我们考虑两种转移:

  1. 从PAM的转移数组转移,显然如果 s o n [ u ] [ i ] = v son[u][i]=v son[u][i]=v,则我们考虑在构造出 u u u的一半的时候加上一个字符 i i i,然后复制成 v v v,则 m n [ v ] = m n [ u ] + 1 mn[v]=mn[u]+1 mn[v]=mn[u]+1,为此我们需要初始化 m n [ 0 ] = 1 mn[0]=1 mn[0]=1
  2. 从fail树上转移,我们找到它的最长的不超过它一半长度的回文后缀,则我们考虑将这个后缀补齐到一半长度,然后复制一次,记这个回文后缀为 h a l f [ v ] half[v] half[v],则 m n [ v ] = l e n [ v ] / 2 − l e n [ h a l f [ v ] ] + m n [ h a l f [ v ] ] + 1 mn[v]=len[v]/2-len[half[v]]+mn[half[v]]+1 mn[v]=len[v]/2len[half[v]]+mn[half[v]]+1

两者取 m i n min min就行了,最终答案为 min ⁡ ( n − l e n [ u ] + m n [ u ] ) \min(n-len[u]+mn[u]) min(nlen[u]+mn[u])


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int get_s(char *s){
		int len=0;char c;
		while(isspace(c=gc()));
		while(s[len++]=c,!isspace(c=gc())&&c!=EOF);
		return s[len]='\0',len;
	}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e5+5;

inline int id(char c){
	switch(c){
		case 'A':return 1;
		case 'T':return 2;
		case 'C':return 3;
		case 'G':return 0;
		default:assert(0);
	}
}

char s[N];int n;
namespace PAM{
	int son[N][4],fa[N],len[N],now,last;
	int mn[N],half[N];
	
	inline void init(){
		while(~now){
			memset(son[now],0,sizeof son[now]);
			fa[now]=len[now]=mn[now]=half[now]=0;
			--now;
		}
		last=0,now=1;
		fa[0]=fa[1]=1,len[1]=-1;
	}
	
	inline void push_back(char c,int i){c=id(c);
		int p=last;
		while(s[i]!=s[i-len[p]-1])p=fa[p];
		if(!son[p][c]){
			len[++now]=len[p]+2;
			int k=fa[p];
			while(s[i]!=s[i-len[k]-1])k=fa[k];
			fa[now]=son[k][c],son[p][c]=now;
			k=half[p];
			while(s[i]!=s[i-len[k]-1])k=fa[k];
			k=son[k][c];
			while(len[k]>(len[now]>>1))k=fa[k];
			half[now]=k;
		}
		last=son[p][c];
	}
	
	inline void solve(){
		int ans=n=get_s(s+1);
		for(int re i=1;i<=n;++i)push_back(s[i],i);
		for(int re i=2;i<=now;++i)if(len[i]&1)mn[i]=len[i];
		mn[0]=1;std::queue<int> q;q.push(0);
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int re i=0,v;i<4;++i)if(v=son[u][i]){
				mn[v]=std::min(mn[u]+1,len[v]/2-len[half[v]]+mn[half[v]]+1);
				ans=std::min(ans,n-len[v]+mn[v]);
				q.push(v);
			}
		}
		cout<<ans<<"\n";
	}
}

signed main(){
//	freopen("virus.in","r",stdin);freopen("virus.out","w",stdout);
	int T;scanf("%d",&T);
	while(T--)PAM::init(),PAM::solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值